X-Sender-Tenant and X-API-Key headers against https://api.senderz.app/api/v1.
What “transactional” changes, and what it does not
Both send endpoints return202 Accepted. The message row is created, the job is queued at elevated priority, and the send pipeline runs it asynchronously. A 202 means accepted for processing, not delivered. Use delivery status to find out what actually happened.
Marking a message transactional relaxes the marketing gates only. It does not relax the identity, cost or abuse gates.
Send email
Recipient
Provideto or contactId. If both are sent, contactId wins and to is ignored. If to does not match an existing contact, a new contact is created for that address.
string
Recipient email address. Trimmed and lowercased before lookup.
string
UUID of an existing contact. Returns
404 if it does not exist or has been
deleted.Sender
string
required
From address. Must be a valid email, on a verified sending domain, with a
verified sender inbox for that exact local part.
string
Display name. Max 255 characters. On the template path this falls back to the
template’s own sender name when omitted.
string
Reply-to address. Must be a valid email. Not subject to the verification
rules above.
Content
Send either manual content or a template. See Sending from a template.string
default:"manual"
Either
manual or template. Optional. If you pass a templateId the
template path is taken whether or not you set this. Setting
contentMode: "manual" together with a templateId is rejected.string
Subject line. Max 500 characters. Required on the manual path. Ignored
when a
templateId is present, because the subject then comes from the
template.string
HTML body. Max 500,000 characters. Required on the manual path. Ignored
when a
templateId is present.string
Plain-text alternative. Max 500,000 characters. Optional. Ignored when a
templateId is present, because the template renders its own plain-text
part.string
UUID of an email template in this workspace. Must be a template whose channel
is
email.object
Flat object of merge values for the template. Values may be strings, numbers,
booleans or
null.Response
Status202 Accepted.
string
The queued message’s UUID. Pass it to
GET /public/messages/{id} to read delivery status. It
is a plain UUID with no prefix.Sender requirements
The From address is checked synchronously, before the message is queued. Three separate conditions must hold, and each has its own error code.1
The domain is added and verified
The domain part of
fromEmail must exist in this workspace and be in the
verified state. Otherwise 400 with code from_domain_not_verified. See
Domains and authentication.2
The domain is not blocklisted
A domain currently listed on the Spamhaus domain blocklist is refused with
400 and code from_domain_blacklisted.3
The exact mailbox is confirmed
A sender address row for that exact local part must exist and be
verified. Adding orders@acme-store.com sends a confirmation email to
that mailbox, and until someone clicks the link in it, sending from it
returns 400 with code sender_inbox_not_verified. See
Sender addresses and names.Errors
The recipient is resolved before the From address is checked. A request
that fails on
sender_inbox_not_verified may still have created the contact
named in to. Retrying the same request after verifying the mailbox reuses
that contact rather than creating a second one.Send SMS
Recipient
Provideto or contactId. If both are sent, contactId wins and to is ignored. If to does not match an existing contact, a new contact is created for that number.
string
Recipient phone number. Max 32 characters.
string
UUID of an existing contact. Returns
404 if it does not exist or has been
deleted.Sender
string
required
The branded Sender name the SMS is sent from. Required. Max 11
characters. It must be one of your workspace’s own
active Sender names.
Matching is case-insensitive.Content
string
default:"manual"
Either
manual or template. Same rules as email.string
Message text. Max 1600 characters. Required on the manual path. Ignored
when a
templateId is present.string
UUID of an SMS template in this workspace. Must be a template whose channel
is
sms.object
Flat object of merge values for the template. Values may be strings, numbers,
booleans or
null.Response
Status202 Accepted.
string
The queued message’s UUID.
Credits
Transactional SMS is paid from the prepaid credit wallet, exactly like marketing SMS. See SMS for how credits are bought and counted. The wallet is checked twice.1
Pre-flight, on the request
The rendered body is measured and priced at one credit per 268 billable
characters, minimum one credit. If the workspace balance is lower than
that, the request is rejected with
403 and code insufficient_credits
before the recipient is resolved and before anything is queued. An
under-funded workspace fails fast rather than silently dropping the
message later.2
A real hold, at send time
The pipeline places a hold for the same number of credits and commits it on
provider acknowledgement. If the balance ran out between the two checks,
the message ends with status
skipped and failure reason
insufficient_credits instead of returning an error, because the request
already returned 202.Non-GSM text, including Hebrew, is measured as Unicode, so the same character
count costs more. Merge values are substituted before the count is taken, so
a template with long merge values can cost more than the raw template text
suggests.
Errors
Checks run in this order: Sender name, template rendering, credits, recipient.The Sender name is checked again at send time. If approval is revoked in the
window between the
202 and the actual dispatch, the message ends with
status failed and failure reason sender_id_not_approved. It is never
quietly rerouted to a different Sender name.Sending from a template
Pass atemplateId on either endpoint to render a Senderz template server-side instead of supplying the body yourself. This keeps content out of your codebase and lets non-developers edit it.
1
Find the template
GET /public/templates lists templates as
value and label pairs and accepts a channel filter. The value is
the templateId.2
Find its merge tags
GET /public/templates/{id}/merge-fields returns { "items": [...] }, the
list of merge tag keys the template actually uses.3
Send it
Pass
templateId plus a flat templateFields object supplying the values
for those keys.How merge values resolve
A merge tag looks like{{orderNumber}}. A tag may carry a fallback after a pipe character, used when the value is missing or empty.
Values are resolved from three sources, later ones winning:
- The resolved contact:
{{firstName}},{{lastName}},{{email}},{{phone}}. - The contact’s custom properties, as
{{custom.yourKey}}. - Your
templateFields, as{{yourKey}}.
templateFields is applied last, a key that collides with a contact field overrides it for this one message. A templateFields value of null is skipped entirely, so the contact value or the tag’s fallback is used instead. Values that are objects or arrays are dropped rather than stringified, so nested data will render as empty.
Check delivery status
A202 only confirms the message was accepted. These two endpoints report what the pipeline did with it.
Get one message
string
required
The
messageId returned by a send. Must be a UUID, otherwise the request
fails validation.string
Message UUID.
string
One of
email, sms, push, voice, whatsapp.string
One of
queued, sent, delivered, bounced, complained, failed,
suppressed, skipped.string | null
Machine-readable reason when the message did not send.
null on the happy
path.string | null
The sending provider’s own identifier, once the provider has accepted the
message.
string
ISO 8601. When the message was queued.
string | null
ISO 8601. When the provider accepted it.
string | null
ISO 8601. When delivery was confirmed.
string
ISO 8601. Last status change.
400 if id is not a UUID, 401 on bad auth, 404 Message not found if the id belongs to another workspace or does not exist, 429 if rate limited.
List messages
integer
default:"50"
Page size. Range 1 to 100.
integer
default:"0"
Records to skip.
limit and offset are the only accepted query parameters. There is no
status, channel or date filter, and sending one returns 400. Filter
client-side, or subscribe to
webhooks for delivery events instead of
polling.Reading a blocked message
Whenstatus is not sent or delivered, failureReason explains why. The values a transactional message can carry include:
Companion endpoints
See Errors and rate limits for the shared error envelope and rate-limit behaviour.

