← barua.tz

Sending#

POST /api/v1/emails is the request at the top of this page. Its checks run in a fixed order, so a refusal tells you which layer said no: the key and its scope; the Idempotency-Key, if any; whether the account may send at all (suspended, or over its daily quota), and for a sub-account whether its parent may; whether the paying account has credit; the body; the from-domain; the suppression list. Only then does anything leave.

from is exactly one mailbox: a bare address, or a display name of up to 80 characters and an address in angle brackets. Anything with a comma or a second address is refused, and the header that goes out is rebuilt from the parsed parts rather than copied from the request, so a crafted string cannot send as somebody else.

Sandbox. Set sandbox: true and every one of those checks still runs, the send is logged with status sandboxed and an id you can fetch back, and nothing is sent, counted or charged. Use it from a test suite: a wrong domain or a suppressed address fails the same way it would in production. Up to 1000 sandbox sends in 24 hours, after which it is 429 sandbox_limit.

Idempotency. A timeout after the mail left looks exactly like one before it. So name the request with an Idempotency-Key (any string up to 255 characters, unique per email; an order number works) and retry freely. The key is claimed before any work is done, so two copies of one request cannot both send. Same key and same body within 24 hours: the first answer comes back, with Idempotent-Replayed: true. Same key, different body: 422 idempotency_mismatch, because handing back the wrong receipt's id would look like success. First request still running: 409 idempotency_in_progress; retry in a few seconds. A claim nobody answered is released after 15 seconds. Only answers that settled the send are filed: a validation error is not, and neither is a 502 send_failed, so a retry under the same key after either is a fresh attempt.

Suppressed recipients. If any recipient is on the suppression list the whole request is refused with 422 recipient_suppressedand nothing is sent. Dropping one address from three would send a message you could not see was incomplete; the error names the addresses so your code can act. A sub-account's send is checked against its own list and its parent's. Remove an address with DELETE /api/v1/suppressions once you know why it was listed.

What a send costs.One credit from the paying account (the parent, for a sub-account), however many recipients it has. The daily quota counts recipients, because that is what receiving servers count, and a sub-account's recipients count against its parent's quota and reputation as well as its own. A send on the free trial, or in the grace window after credits ran out, succeeds with a warning object, so an integration nobody is watching still hears about it before it stops.

POST /api/v1/emails

Send an email. Scope emails:send.

Checks run in a fixed order, so the refusal says which layer said no: the key, its scope and X-Barua-Account; the Idempotency-Key; whether the account may send at all (suspended, or over its daily quota), and for a sub-account whether its parent may; whether the paying account has credit; the body; the from-domain; the suppression list, and the parent's too for a sub-account. Only then does anything leave. With sandbox true every check still runs and the send is logged, but nothing is sent, counted or charged.

curl
curl https://barua.tz/api/v1/emails \
  -H "Authorization: Bearer barua_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: receipt-R-1042" \
  -d '{
    "from": "Duka Langu <receipts@dukalangu.co.tz>",
    "to": "neema@gmail.com",
    "template": {
      "name": "receipt",
      "receiptNumber": "R-1042",
      "total": "25,000",
      "paidOn": "24 September 2026",
      "paymentMethod": "M-Pesa"
    },
    "sandbox": true
  }'
response 200
200
{
  "id": "9b2e6d7a-3c4f-4e5a-9b1c-2d3e4f5a6b7c",
  "sandbox": true
}
400 invalid_jsonThe request body could not be parsed as JSON.
400 invalid_idempotency_keyIdempotency-Key was sent but is empty or longer than 255 characters.
402 no_creditsThe paying account has no credits and its grace window has closed.
403 suspendedSending is paused on the account this request acts for, or on its parent. The message is the reason, written for the sender; a parent's pause is prefixed with: The parent account cannot send.
403 domain_not_yoursThe from-address is on a domain this account has not connected.
403 domain_not_readyThe domain is connected but its ownership record is not verified or sending is not yet enabled for it.
409 idempotency_in_progressThe first request with this Idempotency-Key is still running. Retry in a few seconds to get its answer.
413 body_too_largeThe request body is 1 MB or more.
422 invalid_requestThe body or query failed validation. The message names the first field that failed.
422 recipient_suppressedOne or more recipients are on the suppression list. Nothing was sent; the message names them.
422 idempotency_mismatchThis Idempotency-Key was already used with a different body. Use a new key for each new email.
429 quota_exceededThe daily quota of the account, or of its parent, is used up. It resets at midnight UTC.
429 sandbox_limitThe account has made 1000 sandbox sends in the last 24 hours.
502 send_failedBarua's mail server did not accept the message. The send is logged with status failed. Not filed under the Idempotency-Key, so a retry with the same key is a fresh attempt.