Skip to main content
Sending an email is one call. Pick the inbox it comes from, name the recipients, give it a body — Dairo authenticates the mail, delivers it, and records what happens next. Everything else on this page is optional.

The basic send

Call POST /v1/messages (scope messages:send) with an inboxId — the verified inbox the mail comes from — at least one recipient in to, and one body. That is a complete send.
An immediate send is performed synchronously, so the response is the definitive outcome — you never get an acceptance that fails later on something knowable at submit time.
Keep the id — it keys delivery tracking and the webhook events Dairo emits as the mail progresses. Dairo picks the transport automatically: external addresses go out as email, and when every recipient is another Dairo inbox the message is delivered directly with channel: "a2a" — still returning sent. Pass channel to force one or the other; see Channels.
Provide exactly one body — text, html, react, or template. Two body fields in one request is a 400, and so is a send with no body at all unless it carries at least one attachment (an attachment-only email is valid). text and html together may total at most 1 MB.

Request fields

A send takes at most 50 recipients across to, cc, and bcc combined.

Multiple recipients, CC and BCC

to, cc, and bcc each accept a single address or an array.

Send to a contact

You don’t have to know a recipient’s raw address. If you keep an address book of contacts, reference one and Dairo resolves it against the sending inbox’s channel — for an email inbox, to the contact’s primary email address. Raw addresses in the same list pass through untouched, so you can mix them freely. A contact with no handle for the sending inbox’s channel is a 422 — resolution never guesses an address. The resolved contact is stamped on the outbound message, so the send joins that contact’s cross-channel history.

Send a React email

Pass a react object and Dairo renders your React Email component into HTML — no build step or render service on your side. Send the component source plus any props; imports are limited to React Email components. source may be up to 64 KiB and props up to 32 KiB of JSON.
To reuse a design across many sends, save it once as a template and send it by id instead.

Attach files

Each attachment is an object, and you attach it one of two ways — never both:
  • Inline bytes — base64-encode the file into contentBase64. Requires a filename; contentType is recommended. Best for small files.
  • Storage reference — pass the objectId of a Dairo storage object you own (the id a bucket upload returns on finalize). Dairo fetches the bytes and attaches them natively, deriving filename and contentType from the object; override either in the same attachment object.
Limits: up to 10 attachments per send. Inline bytes are capped at 8 MiB per file and 8 MiB total across all inline attachments — they ride the API request itself. A storage reference attaches up to 24 MiB. Over these limits the send returns 413; Dairo never silently drops an attachment or edits your body.
For a file larger than 24 MiB, share a link instead: create a share link for the stored object and place it in your text or html yourself. Dairo never rewrites your message or inserts links on its own — an attachment with delivery: "link" is rejected for exactly this reason.

Test a send without sending it

Set dryRun: true and Dairo validates the request exactly as it would a real send — the body rules, the recipients, the sending inbox, complaint suppression, your attachments — resolves which channel it would go out on, and then stops.
cURL
Response
Nothing is written, queued, handed to the provider, metered, or billed, and there is no message id — a preview has no row to fetch and will never emit delivery events. A bad payload still fails the way it normally would: a dry run validates, it does not wave anything through.
dryRun is a per-request control. The dairo_test_ / dairo_live_ prefix on an API key is a cosmetic label with no effect on delivery — a dairo_test_ key sends real mail to real people and is billed identically. If you want to send nothing, say so on the request.

Prevent duplicate sends

Pass an idempotencyKey (or the Idempotency-Key header) and a retried request won’t send a duplicate — Dairo returns the result of the original send instead.
Build the key from the thing you’re emailing about — an order ID, an event ID, a row’s primary key — so the same logical email always carries the same key. See Retries & idempotency.

Schedule for later

Add sendAt to any send and Dairo holds the email until then — useful for reminders, drip sequences, and time-zoned announcements. sendAt is an RFC 3339 timestamp with an explicit timezone offset (for example 2026-07-01T09:00:00-04:00, or …Z for UTC — a bare local time is a 400). It must be in the future, at most 30 days ahead.
The response comes back with status: "scheduled" and the exact scheduledAt time the email will go out, normalized to UTC.
At fire time Dairo re-checks the send — inbox, domain, complaint suppression, and quota — and either delivers it or marks it failed. A recipient who reported spam after you scheduled the email is still protected.

Cancel a scheduled send

While an email is still scheduled, POST /v1/messages/{messageId}/cancel stops it for good and sets its status to canceled. A send that is no longer scheduled — already queued, sent, failed, or canceled — returns a 409.

Recipients who reported spam

Dairo remembers every recipient who marked your mail as spam and refuses to email them again: a send to such a recipient fails with a 400 naming the address. Set ignoreComplaints: true to override deliberately — the send then proceeds and the response’s warnings array lists each affected recipient:
Emailing people who reported you as spam damages your domain’s reputation for every inbox you send from, and can push future mail into spam folders. Override only when you are certain — and note that the override never applies to scheduled sends: a complaint on file at fire time marks the send failed. See Land in the inbox.

List and read your sends

List outbound messages (most recent first) with GET /v1/messages filtered on direction: "outbound". To follow one send, use the id the send call returned: fetch it for its current status, or list its per-recipient delivery events.

Next steps