[email protected].
Before you start
- Create the mailbox in Ozi first, under Mailboxes. The API reads and sends mail for mailboxes that already exist; it does not create them.
- Use an API key with Full access. Keys set to Sending only - the kind
you hand to an app that just sends notifications - get
403 FORBIDDENfrom every mailbox endpoint, because these endpoints read people’s mail. - A key restricted to one domain only sees that domain’s mailboxes.
Conversations, messages and attachments outside the key’s team or domain
return
404, exactly as if they did not exist.
Endpoints
Fields in mailbox responses use
snake_case, such as next_cursor and
last_message_at.
Using the SDK
How it behaves
- Reading does not change anything. Getting a thread never marks it read.
Mark it read yourself with
PATCH /v1/threads/{id}. - Paging is stable. Threads come newest first. Pass
next_cursorback ascursor; new mail arriving while you page lands on the first page and never shifts the pages after it. - Drafts are left out unless you ask for
folder=drafts, and messages a mail app has marked for deletion never appear. - Long bodies are cut, not refused. A message’s
textandhtmlare trimmed in API responses when they are very large. A thread returns its latest 100 messages. - Replies go to the person who wrote last, and are filed in the mailbox’s Sent folder like a reply from webmail. They can be plain text or formatted (see below).
- New messages start their own thread.
POST /v1/mailboxes/{id}/messagessends to up to three addresses, gets the mailbox signature, is filed in Sent and returns the newthread_id. It never joins an existing conversation, even one with the same subject. A recipient on your suppression list refuses the whole message (recipient_suppressed) rather than sending to the others. - Attachments download as files. They always come back with
Content-Disposition: attachment. Images embedded in a message body are not listed and cannot be downloaded through the API, and attachments over 25 MB were never stored, so they return404with a message saying so.
Formatted messages, pictures and scheduling
Replies and new messages take the same optional fields as webmail’s composer. Leave them all out and a message is plain text, sent at once, exactly as before.htmlis a formatted body, up to 2 MB. It is cleaned on the server with the same rules webmail uses for what people write: scripts, forms, event handlers,<style>blocks, frames and unsafe links are removed before it is sent, so send ordinary formatting (paragraphs, bold, lists, links, tables, inline styles, pictures). When you leave outtext, the plain-text copy is made from the HTML. The mailbox signature is added on the server: above an element withclass="ozi_quote"when the body has one (wrap the quoted message you reply to in it), otherwise at the end. Do not add it yourself.inline_imagesare pictures shown in the HTML, up to 10, each{ "cid", "filename", "content_type", "content_base64" }. Show one with<img src="cid:logo">.cidis 1-64 letters, digits,.,_or-, and unique in the message.content_typeisimage/png,image/jpeg,image/giforimage/webp, and the bytes must really be that type: they are checked. Each picture can be up to 1,350,000 bytes (the largest picture webmail’s composer holds). Everycid:the HTML shows must be supplied, and every picture supplied must be shown; otherwise the request is refused with400and nothing is sent. The filename’s extension is corrected to match the type.draft_idnames a draft of the same mailbox that this message replaces, such as the one a cancelled send became. Once the message is sent (or scheduled) the draft is deleted, as webmail does. It must be in that mailbox’s Drafts folder; anything else returns404and nothing is sent.send_at(RFC 3339, such as2026-10-09T09:00:00Z) holds the message until then, up to a year ahead. It waits in the mailbox’s Scheduled folder, where webmail shows it too, and the response has"scheduled": true, thescheduled_idand thesend_at. A mailbox can have up to 100 messages waiting.
Undo send
GET /v1/mailboxes gives each mailbox’s undo_send_seconds, the delay its
owner chose for Undo in webmail (0 when it is off). To offer the same undo,
send with send_at that many seconds from now - send_at may be as soon as
now - and if the person clicks Undo, cancel it with
DELETE /v1/scheduled/{scheduled_id}. The cancel returns the message’s
subject, recipients, HTML (with its pictures as data: URLs) and text, to put
back in your composer, and keeps it as a draft in the mailbox’s Drafts folder
under draft_id. Pass that draft_id when you send it again, so the draft
does not stay behind in Drafts.
Changing a waiting message
GET /v1/mailboxes/{id}/scheduled lists what a mailbox has waiting, soonest
first. POST /v1/scheduled/{id}/send-now sends one at once, and
PATCH /v1/scheduled/{id} with { "send_at": ... } moves it (at least a
minute ahead). Once a message has gone, or is on its way, these return
409 already_sent and change nothing.
Saved replies
Each mailbox keeps up to 100 saved replies, the same ones webmail’s composer offers:{ "id", "name", "html", "updated_at" }. Names are 1-80 characters;
the HTML, up to 100,000 characters, is cleaned on the server like a message
body, and the cleaned form is what is returned.
Getting told about new mail
Rather than checking for new mail on a timer, add a webhook for these events:
Both carry the same data, including the
threadId to fetch with
GET /v1/threads/{id}: