Skip to main content
Transactional messages are one-off sends triggered by the recipient’s own action: order confirmations, shipping notices, receipts, password resets, appointment reminders. Senderz exposes one endpoint per channel, plus two endpoints for reading back what happened to a message after it was queued. Two channels are available on the public API: email and SMS. There is no public push, WhatsApp or voice send endpoint. One-time passcodes have their own surface, documented in OTP API. Every request uses the 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 return 202 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.
Suppression still applies, but a narrower set of reasons blocks a transactional message. Only hard_bounce, invalid_address and global_blocklist stop it. A marketing unsubscribe, a spam complaint, an SMS STOP reply, a repeated soft bounce, a list cleanup and a revoked push endpoint do not stop a transactional send. A blocked message is recorded with status suppressed and the suppression reason.

Send email

Recipient

Provide to 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

Status 202 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.
GET /public/sender-addresses returns exactly the addresses that will pass all three checks. Its value field is the address to put in fromEmail.

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

Provide to 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.
Send to in E.164 form, for example +972501234567. The contact lookup is an exact string match against the stored number, and stored numbers are always E.164. A local form such as 0501234567 will not match the existing contact, so Senderz tries to create a second one, normalises it to +972501234567, finds the number already taken and returns 409 with code contact_phone_taken.

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.
There is no default and no platform fallback. Omitting senderId fails validation. Sending an empty value, or one of the shared platform names SENDER, INFO or SHOP, returns 400 with code sender_id_required. Sending a name that is not approved for your workspace returns 400 with code sender_id_not_approved. Request and track Sender names at SMS Sender names, or list the approved ones with GET /public/sms-sender-ids.

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

Status 202 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 a templateId 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:
  1. The resolved contact: {{firstName}}, {{lastName}}, {{email}}, {{phone}}.
  2. The contact’s custom properties, as {{custom.yourKey}}.
  3. Your templateFields, as {{yourKey}}.
Because 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.
On the SMS endpoint the merge context is built from an existing contact only. If to is a number Senderz has never seen, the contact is created after rendering, so contact tags such as {{firstName}} render as empty or fall back on that first message. The email endpoint resolves or creates the contact before rendering, so contact tags always resolve there. Supply the values through templateFields if you need them on a first-touch SMS.

Check delivery status

A 202 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.
Errors: 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

Returns every message the workspace has sent, newest first. This is not limited to messages sent through this API: campaign and automation sends appear here too.
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.
The response is the standard paginated shape; each item has the same fields as a single message.

Reading a blocked message

When status is not sent or delivered, failureReason explains why. The values a transactional message can carry include:
Rather than polling, subscribe to delivery webhooks and treat these endpoints as the reconciliation path.

Companion endpoints

See Errors and rate limits for the shared error envelope and rate-limit behaviour.