sendbaze
Menu

Reference

API reference

Base URL https://api.sendbaze.com/v1. JSON in, JSON out. Bearer authentication on every endpoint.

Request and response headers

FieldTypeDescription
AuthorizationrequiredrequestBearer sk_live_… or Bearer sk_test_….
Idempotency-KeyrequestUp to 255 characters. Makes a POST safe to retry for 24 hours.
x-request-idresponseUnique per request. Quote it when you contact support.
x-ratelimit-limitresponseRequests per second allowed for your organisation.
x-ratelimit-remainingresponseRequests left in the current second.
Retry-AfterresponseSeconds to wait. Sent with 429 responses only.
Idempotent-Replayedresponsetrue when the response was served from an earlier request with the same key.

Emails

Send an email

POST/v1/emails

Validates the request, writes the message, queues it and returns the id. The email leaves through Amazon SES in the Africa (Cape Town) region within seconds. Requires the emails:send scope and a verified sending domain.

FieldTypeDescription
fromrequiredstringA verified sender. orders@yourshop.co.za or Yourshop <orders@yourshop.co.za>.
torequiredstring | string[]One address or up to 50.
ccstring | string[]Up to 50.
bccstring | string[]Up to 50.
replyTostringWhere replies go if not the sender.
subjectrequiredstringUp to 998 characters.
htmlstringHTML body, up to 2 MB. One of html, text or templateId is required.
textstringPlain-text body. Sent alongside html if both are set.
templateIdstringA template from the dashboard. Cannot be combined with html or text.
variablesobjectString, number or boolean values for template placeholders. Requires templateId.
headersobjectCustom headers. Standard headers such as From, To, Subject and DKIM-Signature cannot be overridden.
tagsstring[]Up to 10 tags of 64 characters, returned on events and searchable in the logs.
class"transactional" | "marketing"Defaults to transactional. Marketing mail gets List-Unsubscribe headers and requires a lawful basis for each recipient.
attachmentsobject[]Coming in the private beta. Requests with attachments are rejected until then.
terminal
curl https://api.sendbaze.com/v1/emails \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-shipped" \
  -d '{
    "from": "Yourshop <orders@yourshop.co.za>",
    "to": ["thandi@example.com"],
    "replyTo": "help@yourshop.co.za",
    "subject": "Your order #1042 has shipped",
    "html": "<p>Tracking: <b>CG123456789ZA</b></p>",
    "text": "Tracking: CG123456789ZA",
    "tags": ["order-shipped"]
  }'

# 200 OK
# { "id": "em_01j9x7k2r4m8q3v5n6b7c8d9e0", "status": "queued" }

Send a batch of emails

POST/v1/emails/batch

Up to 1 000 emails in one request. Each item is validated and queued on its own, so a bad item fails alone. Results come back in input order. Idempotency-Key is not applied to individual items; use tags to reconcile.

terminal
curl https://api.sendbaze.com/v1/emails/batch \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "emails": [
      { "from": "orders@yourshop.co.za", "to": "a@example.com", "subject": "Hi", "text": "..." },
      { "from": "orders@yourshop.co.za", "to": "not-an-email", "subject": "Hi", "text": "..." }
    ]
  }'

# 200 OK
# {
#   "results": [
#     { "index": 0, "ok": true,  "data": { "id": "em_01j9...", "status": "queued" } },
#     { "index": 1, "ok": false, "error": { "code": "validation_error", "message": "...",
#                                           "fields": { "to": ["must be a valid email address"] } } }
#   ]
# }

Retrieve an email

GET/v1/emails/{id}

Returns the message object with its full event timeline. Requires the read scope.

Domains

Live keys may only send from domains your organisation has verified. Add a domain, publish the DNS records the response lists at your registrar, and the status moves to verified once Amazon SES confirms the DKIM and MAIL FROM records (usually minutes, at most 72 hours). Sending from a subdomain of a verified domain needs no extra set-up. Test keys skip this check because they never deliver.

Add a domain

POST/v1/domains

Creates the sending identity and returns the records to publish: three DKIM CNAMEs, an MX and an SPF TXT for the custom return-path host (mailFromSubdomain, default mail), and a suggested DMARC record. Each record carries our last DNS lookup in status and found, so a wrong value is visible rather than a silent pending. Adding a domain you already have returns it with 200. Requires domains:write.

terminal
curl https://api.sendbaze.com/v1/domains \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "yourshop.co.za" }'

# 201 Created
# {
#   "id": "dom_01j9x7k2r4m8q3v5n6b7c8d9e0",
#   "name": "yourshop.co.za",
#   "status": "pending",
#   "mailFromDomain": "mail.yourshop.co.za",
#   "records": [
#     { "purpose": "dkim", "type": "CNAME", "name": "abc123._domainkey.yourshop.co.za",
#       "value": "abc123.dkim.af-south-1.amazonses.com", "required": true, "status": "pending", "found": [] },
#     ... two more DKIM CNAMEs ...
#     { "purpose": "mail_from_mx", "type": "MX", "name": "mail.yourshop.co.za",
#       "value": "10 feedback-smtp.af-south-1.amazonses.com", "required": true, "status": "pending", "found": [] },
#     { "purpose": "mail_from_spf", "type": "TXT", "name": "mail.yourshop.co.za",
#       "value": "v=spf1 include:amazonses.com ~all", "required": true, "status": "pending", "found": [] },
#     { "purpose": "dmarc", "type": "TXT", "name": "_dmarc.yourshop.co.za",
#       "value": "v=DMARC1; p=none; rua=mailto:dmarc@yourshop.co.za", "required": false, "status": "pending", "found": [] }
#   ],
#   "verifiedAt": null, "lastCheckedAt": "2026-09-23T18:40:12.000Z", "createdAt": "2026-09-23T18:40:11.000Z"
# }

List and retrieve domains

GET/v1/domains

GET/v1/domains/{id}

The list is newest first under data. Statuses are re-checked every five minutes while pending and daily once verified. Requires read.

Check a domain now

POST/v1/domains/{id}/verify

Asks SES and public DNS immediately instead of waiting for the next poll, and returns the updated domain. Use it after publishing records. Requires domains:write.

Delete a domain

DELETE/v1/domains/{id}

Removes the identity. Sends from that domain fail with domain_not_verified from the next request. Returns { "id": "dom_…", "deleted": true }.

Suppressions

Addresses your organisation will not send to. A send whose to, cc or bcc includes one fails with 422 recipient_suppressed before anything is queued, so a bad address never costs you reputation twice. Entries arrive four ways: hard_bounce (the mailbox does not exist; applied for every sender on Sendbaze), complaint (the recipient marked your email as spam), unsubscribe (they used the List-Unsubscribe link on a marketing email; blocks marketing only, transactional still sends) and manual (you added it). Test keys skip the check.

Marketing email (class: "marketing") automatically carries List-Unsubscribe and one-click headers, so Gmail and Yahoo show an unsubscribe button. Put {{unsubscribe_url}} in your HTML or text to add a visible link too. If the complaint rate reaches 0.1% with at least 3 complaints in 24 hours, sending is paused (423 org_paused) and the owners are emailed.

List suppressions

GET/v1/suppressions

Newest first under data, cursor-paged with nextCursor. Query parameters: email (substring match), limit (1–200, default 50), cursor. Requires read.

terminal
curl "https://api.sendbaze.com/v1/suppressions?limit=50" \
  -H "Authorization: Bearer sk_live_..."

# 200 OK
# {
#   "data": [
#     {
#       "id": "sup_01j9x7k2r4m8q3v5n6b7c8d9e0",
#       "email": "gone@example.co.za",
#       "reason": "hard_bounce",
#       "messageId": "em_01j9x7k2r4m8q3v5n6b7c8d9e1",
#       "note": "smtp; 550 5.1.1 user unknown",
#       "createdAt": "2026-10-27T09:12:44.000Z"
#     }
#   ],
#   "nextCursor": null
# }

Add a suppression

POST/v1/suppressions

Body: email (required), reason (manual blocks all email, the default; unsubscribe blocks marketing only) and an optional note. Adding an address that is already listed returns the existing entry with 200. Requires emails:send.

terminal
curl https://api.sendbaze.com/v1/suppressions \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "email": "person@example.co.za", "reason": "manual", "note": "Asked to be removed" }'

# 201 Created (200 if it was already listed)

Remove a suppression

DELETE/v1/suppressions/{id}

Removes one of your own entries so the address can receive email again. Returns { "id": "sup_…", "deleted": true }. Global hard-bounce entries are not yours to remove; contact support if a mailbox has come back to life. Requires emails:send.

SMS

Send an SMS

POST/v1/sms

Counts segments (GSM-7 or UCS-2), checks credits, applies the compliance rules for the message class, debits credits and queues the message. Credits are refunded automatically if the message never leaves our platform. Requires the sms:send scope.

FieldTypeDescription
torequiredstringE.164, for example +27821234567.
bodyrequiredstringUp to 1,600 characters or 10 segments. The response tells you how many segments it used.
classrequired"transactional" | "marketing"Marketing messages need a consent record for the number, an opt-out footer, and are held to 08:00 to 20:00 SAST on weekdays and Saturdays.
senderIdstringAn approved alphanumeric sender of up to 11 characters, or a number you own. Defaults to your organisation's shared sender.
scheduledAtstringISO 8601 timestamp up to 30 days ahead. The response status is "scheduled".
tagsstring[]Up to 10 tags.
terminal
curl https://api.sendbaze.com/v1/sms \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+27821234567",
    "body": "Your OTP is 482 913. It expires in 5 minutes.",
    "class": "transactional"
  }'

# 200 OK
# { "id": "sm_01j9x7n4q6r8s0t2u4v6w8x0y2", "status": "queued", "segments": 1, "credits": 1 }
Marketing SMS outside the legal window is not rejected. It is held and sent at 08:00 SAST on the next permitted day, and the response status is scheduled.

Send a batch of SMS

POST/v1/sms/batch

Same shape as the email batch with a messages array of up to 1 000 items. If credits run out part-way, the remaining items fail with insufficient_credits and the earlier ones still send.

Retrieve an SMS

GET/v1/sms/{id}

Returns the message object with segments, credits, scheduledAt and the event timeline.

Usage

GET/v1/usage

Your plan, the current billing period, how much of each allowance is used and remaining, prepaid SMS credits, and domains in use. Allowances reset at period.end. When an email allowance is used up, sends fail with plan_allowance_exceeded until the reset or an upgrade; SMS continues on prepaid credits once its allowance is gone. Test keys never count. Requires read.

terminal
curl https://api.sendbaze.com/v1/usage -H "Authorization: Bearer sk_live_..."

# 200 OK
# {
#   "plan": { "id": "starter", "name": "Starter", "priceRand": 249 },
#   "period": { "start": "2026-10-23T18:00:00.000Z", "end": "2026-11-23T18:00:00.000Z" },
#   "emails": { "used": 4210, "included": 10000, "remaining": 5790 },
#   "sms": { "used": 300, "included": 300, "remaining": 0, "credits": 1000 },
#   "domains": { "used": 1, "included": 2 }
# }

The message object

FieldTypeDescription
idstringem_… for email, sm_… for SMS.
channel"email" | "sms"
classstringtransactional or marketing.
statusstringqueued, sent, delivered, bounced, complained, failed, expired or rejected. See below.
tostringRecipient address or number.
fromstringSender address or sender ID.
subjectstring | nullEmail only.
segmentsnumberSMS only.
creditsnumberSMS only. Credits debited for this message.
tagsstring[]
error{ code, message } | nullSet once the message has failed, bounced or been rejected.
createdAtstring
sentAtstring | null
deliveredAtstring | null
eventsobject[]The full timeline, oldest first: { type, occurredAt, detail }.

Statuses

  • queued: accepted and waiting for a worker. Usually under a second.
  • sent: handed to Amazon SES or the mobile network.
  • delivered: the receiving server or handset confirmed delivery.
  • bounced and complained: email only. Both suppress the address.
  • failed: permanent failure after retries. error says why.
  • expired: SMS only. The network could not deliver within its validity period.
  • rejected: blocked before sending, for example a suppressed address or a missing consent record.

Idempotency

Send an Idempotency-Key with any POST. For 24 hours the same key and body return the original response with an Idempotent-Replayed: true header. The same key with a different body or on a different channel returns 409. If a request fails before the message is queued, the key is released so your retry goes through.

terminal
# First request
curl https://api.sendbaze.com/v1/sms -H "Idempotency-Key: otp-4821-1" ...
# 200 { "id": "sm_01j9...", ... }

# Same key, same body, within 24 hours
curl https://api.sendbaze.com/v1/sms -H "Idempotency-Key: otp-4821-1" ...
# 200 { "id": "sm_01j9...", ... }   header: Idempotent-Replayed: true

# Same key, different body
# 409 { "error": { "code": "idempotency_conflict", ... } }

Rate limits

100 requests per second per organisation, shared across all its keys. The batch endpoints count as one request each, so bulk sends should use them. Above the limit the API returns 429 with Retry-After.

Test mode

Keys that start with sk_test_ run every validation and compliance check and return an id, but never enqueue, never debit credits and never send. The send response carries "mode": "test" (live keys return "live"), test messages appear in the logs with a test badge, and GET lookups show status sent. Use them in CI and staging; to reach a real inbox, create a live key.

Errors

Every error shares one envelope with a stable code. See the errors reference.