Skip to main content
Messages are the emails an identity receives and sends. Every inbound email is classified on arrival — otp, magic_link, or other — with the code or link extracted, so an agent can wait for exactly the verification email it needs instead of parsing mail itself. Most of the time you don’t need this API at all: during a register or login run, Sente reads the verification email and types the code itself. Use these endpoints when your own code drives the flow, or when you want to see what actually arrived.

The message object

Annotation runs immediately after the message is persisted, before the message.received webhook fires — but a message you read through GET /v1/messages microseconds after arrival can still show annotation: null. Filtering by kind sidesteps this entirely: an unannotated message simply doesn’t match, so wait?kind=otp never returns a message whose code hasn’t been extracted yet.

Wait for a verification code

GET /v1/messages/wait Holds the HTTP request open until a matching message arrives, then returns it. This is the primitive behind “wait for the OTP”. Returns 200 with the oldest matching message, or 204 with no body when the timeout elapses with nothing matching. On 204, call again with the same since so nothing is skipped.
The robust pattern: stamp since yourself before triggering the action that sends the email, then pass it explicitly. The 60-second default lookback covers most timing gaps, but an explicit since is exact — and it lets you re-poll without matching a code from the previous attempt.
Response 200 — a message object; the code is at annotation.code, a magic link at annotation.link:

List messages

GET /v1/messages
Response 200: an array of message objects, newest first. Every row carries its own identityId, so an org-wide listing can be grouped per identity without a second call. Passing an identityId that isn’t your org’s returns 404 {"error": "identity not found"}. Omit it and there is no 404 to hit — the query is org-scoped, so another org’s mail is never included.
The org-wide listing is HTTP-only: messages.list in both SDKs takes an identityId as its first argument, and sente inbox requires --identity. Use it for dashboards and audits; for “wait for the code that is about to arrive”, use wait above.

Get a message

GET /v1/messages/:id
Response 200: the full message object, including parsed and annotation. 404 {"error": "not found"} otherwise. This is the call to make after a message.received webhook, whose payload is only a summary.

Send an email

POST /v1/messages Sends from the identity’s own address (DKIM-signed) and persists an outbound message.
Response 201:
deliveryStatus: "sent" means the provider accepted it, not that it was delivered. Re-fetch the message later to see the delivery, bounce, or complaint outcome. Both caps are checked before the mail provider is called, so a 402/429 never sent anything.

Archive and unarchive

POST /v1/messages/:id/archive · POST /v1/messages/:id/unarchive Archiving is inbox hygiene: archived messages drop out of GET /v1/messages unless includeArchived=true, and are never matched by wait. Both are idempotent and return the updated message object. No body.
Response 200: the message object with "archived": true. 404 {"error": "not found"} for an unknown id or another org’s message.
Archiving an unread verification email hides it from a wait call that is still polling. Archive after you’ve used the code, not before.

Verification codes

The full OTP and magic-link flow.

Inbox

Send, reply, archive, and the org-wide feed.

Webhooks

Get pushed each inbound email instead of polling.