Emails

Every parameter of a send, attachments, idempotency and batches.

Sending is one route: POST /v1/send. Everything else on this page — templates, attachments, scheduling, batches — is a field on that same call or a variation of it.

Requires the send scope.

Send an email

curl https://api.hamanimail.com/v1/send \
  -H "Authorization: Bearer hme_yourkeyid_yoursecret" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Your Business <hello@yourbusiness.com.au>",
    "to": "customer@example.com",
    "subject": "Welcome",
    "category": "transactional",
    "html": "<p>Thanks for signing up.</p>"
  }'

Response 202 Accepted:

{ "status": "queued", "messageId": "…", "idempotencyKey": "…" }

202 means accepted for delivery, not delivered. Delivery is reported afterwards through webhooks and the email log.

Parameters

FieldRequiredNotes
fromyesyou@yourdomain or Name <you@yourdomain>. The domain must be one you have verified.
toyesrecipient address, or Name <address>.
subjectyesplain text.
htmlone of html / text / templateIdthe HTML body.
text—the plain-text body. Send it alongside html: some clients prefer it, and its absence is a spam signal.
replyTo—where replies should go, if not from.
locale—BCP-47 tag (en-AU, fr). Picks the language of a multi-locale template. Defaults to en.
categoryyesa lowercase slug. transactional or marketing for most mail — see below.
templateId + data—render a saved template with {{variables}} from data.
attachments—see Attachments.
idempotencyKey—see Safe retries.
sendAt—ISO-8601 time in the future. See Scheduled sends.
headers—extra headers, from a fixed allowlist. See Custom headers.
senderIdentity—Australian Spam Act sender identification. See below.

Unknown fields are rejected, not ignored. A typo like subjectt returns 400 invalid_request naming the field, rather than silently sending an email with no subject. There is no cc or bcc on this route — send one message per recipient, or use a batch.

category is not optional, and it matters

ValueUse it forWhat we do
transactionalreceipts, sign-in codes, password resets, order updates — mail the recipient asked for by doing somethingdelivered on the transactional path
marketingnewsletters, announcements, promotions — mail you decided to senda one-click unsubscribe header is added automatically, and the recipient's unsubscribe choice is honoured on every future marketing send

Labelling marketing mail as transactional to skip the unsubscribe is a breach of the Acceptable Use Policy and of Australian law. It also damages the delivery of your genuine transactional mail, because complaints against your domain do not distinguish between the two.

Custom headers

headers takes a map of header name to value, but only from this allowlist — anything else is rejected with 400 invalid_request.

HeaderUse
List-Unsubscribe, List-Unsubscribe-Postunsubscribe. Added automatically on marketing sends; set them yourself only if you are managing your own.
List-Id, List-Help, List-Subscribe, List-Archive, List-Owner, List-Postmailing-list identification
Precedencemark bulk mail as bulk
Auto-Submittedmark machine-generated mail, so auto-responders stay quiet
In-Reply-To, Referencesthreading — reference another message so replies group correctly
any header beginning X-your own custom headers

Identity and routing headers are not settable. You cannot override From, To, Subject, Reply-To or anything similar through this field — those come from the validated request fields, which is what stops a caller forging a sender.

Header values may not contain control characters or line separators. That is what blocks header injection: a value with a newline in it is rejected rather than becoming two headers.

Australian sender identification

The Spam Act 2003 requires commercial email to identify the sender and give a way to contact them. Pass senderIdentity and a compliant footer is appended to a marketing send for you.

"senderIdentity": {
  "businessName": "Your Business Pty Ltd",
  "abn": "48 696 864 981",
  "contact": "hello@yourbusiness.com.au"
}
FieldRequiredNotes
businessNameyesthe name the recipient will recognise.
abn—validated against the ATO checksum. A wrong ABN is rejected rather than printed.
contact—an address or phone number the recipient can reach you on.

This does not make your mail compliant on its own — consent and unsubscribe are still yours to get right. It handles the identification part correctly.

Errors

StatuserrorMeaning
400invalid_requestmalformed JSON or a field failed validation — issues[] names the fields.
400recipient_refusedthe recipient looks undeliverable before we try — findings[] explains why.
403from_domain_not_allowedthis key may not send from that domain. Verify the domain, or check the key's scope.
409send_in_progressan identical send is already in flight. Retry shortly; it will not be sent twice.
413attachments_too_largean attachment, or the total body, exceeds the limits.
429rate_limitedover your per-minute limit. Back off and retry.
503temporarya temporary fault. Retry with the same idempotencyKey.

The full list across every route is on the Errors page.

Attachments

"attachments": [
  { "filename": "invoice.pdf", "contentType": "application/pdf", "content": "<base64>" }
]

content is the file's base64 bytes. A contentId marks a file as inline, which you then embed in the HTML as cid:<contentId>:

"attachments": [
  { "filename": "logo.png", "contentType": "image/png", "content": "<base64>", "contentId": "logo" }
]
<img src="cid:logo" alt="Your Business">

Limits. The request body may be up to 6 MB including the base64-encoded attachments (about 4 MB of files per message), and up to 10 MB per file is accepted by the schema, but the body cap governs. 20 files per message.

The gap between those two numbers catches people out: base64 adds about a third to a file's size, so a single 10 MB file can never fit inside a 6 MB body however the schema describes it.

Base64 grows your payload. If you are close to the cap, host the file and send a link instead — it also delivers better, because large attachments are a spam signal.

Safe retries

Pass an idempotencyKey — your order id, invoice number, or any stable string for that one message — and retrying is always safe. The same key never sends twice.

{ "from": "…", "to": "…", "subject": "Your receipt", "idempotencyKey": "order-10482" }

A retried duplicate comes back as:

{ "status": "duplicate", "messageId": "…" }

with the original messageId, so your records stay correct.

If you omit the key, one is derived from the message content, so accidental double-submits are still caught. Supply your own where you can — a derived key cannot tell "the customer ordered the same thing twice" from "our retry logic fired twice".

This is what makes a 503 safe: retry with the same idempotencyKey and you cannot double-send.

Send to many recipients

POST /v1/send/batch — one call, shared content, up to 500 recipients. Each recipient can carry its own data for personalisation.

{
  "from": "Your Business <hello@yourbusiness.com.au>",
  "subject": "Hi {{name}}",
  "templateId": "welcome",
  "category": "marketing",
  "recipients": [
    { "to": "a@example.com", "data": { "name": "Alex" } },
    { "to": "b@example.com", "data": { "name": "Bailey" } }
  ]
}

Each recipient is processed independently, so one bad address never stops the rest. The response carries a per-recipient result — check it, rather than assuming a 202 means all 500 were accepted.

Marketing batches must have unsubscribe configured before they will send.

Retrieve one message

GET /v1/messages/:messageId

Returns the message's current status and every delivery event recorded against it, oldest first:

{
  "status": "delivered",
  "events": [
    { "type": "queued", "at": 1756800000000 },
    { "type": "sent", "at": 1756800002000 },
    { "type": "delivered", "at": 1756800004000 }
  ]
}
StatuserrorMeaning
404message_not_foundno such message for this key — a key only ever sees its own mail.
503messages_unavailablecould not read the log right now. Retry shortly.