Inbound messages
Receive email at a domain you have verified with Sendoka, and SMS replies at a
number you own. Every received message is stored, announced by webhook
(inbound.email / inbound.sms), and readable back with GET /v1/inbound —
the webhook is the fast path, the API the recovery path.
SMS replies need no setup beyond a number in a messaging pool; the rest of this page is about email.
Receive email at a domain
Verify the domain for sending first (see domains.md). Inbound is refused on a
pendingdomain with409 DOMAIN_NOT_VERIFIED, and on your organization's{org-slug}.sandbox.sendoka.comsender with409 DOMAIN_PLATFORM_MANAGED.Enable inbound — Overview → Domains → the domain → Inbound email → Enable, or:
curl -X POST https://www.sendoka.com/api/v1/domains/dom_123/inbound \ -H "Authorization: Bearer $SENDOKA_KEY"{ "domain_id": "dom_123", "domain": "acme.com", "enabled": true, "available": true, "region": "us-east-1", "mx_record": { "type": "MX", "name": "acme.com", "value": "inbound-smtp.us-east-1.amazonaws.com", "priority": 10 }, "simulated": false }Publish the MX record from
mx_recordat your DNS provider. Mail starts arriving once it resolves.
GET /v1/domains/{id}/inbound reads the current state and the record again;
DELETE turns inbound off (remove the MX record too — messages already
received are kept). Enabling and disabling are idempotent and need
write:domains; reading needs read:domains. In the dashboard, any member can
see the state and only developers and owners can change it. Removing the
domain also turns its inbound off.
Inbound follows verification. If the domain stops being verified (its DKIM
records are removed or changed, and a recheck notices), inbound is turned off at
the same time and you receive domain.unverified. Mail sent to the domain is
then rejected and bounced to the sender, not accepted and lost. Once the domain
verifies again, enable inbound again.
The MX record takes over the domain's mail. A domain has one set of MX records, and mail goes to whichever host they name. If people already receive mail at
acme.comthrough Google Workspace, Microsoft 365 or any other mailbox provider, do not pointacme.com's MX at Sendoka — add a subdomain such asinbound.acme.comas its own domain, verify it, and enable inbound there. Replies can be steered to it with aReply-Toheader on what you send.
Inbound accepts every address at the domain (support@, anything@) but not
its subdomains — x.acme.com is a domain of its own and needs its own row.
Test keys
With a sok_test_* key, POST and DELETE run the same checks (verified,
not the sandbox sender) and answer simulated: true without changing anything;
GET answers enabled: false, simulated: true because a test key does not
read live routing. Use a live key to actually receive mail.
Errors
| Code | When |
|---|---|
DOMAIN_NOT_VERIFIED (409) |
The domain is still pending. |
DOMAIN_PLATFORM_MANAGED (409) |
The org's sandbox sender. |
INBOUND_REGION_IDENTITY_MISSING (409) |
Mail is received in one region (region in the response) and the domain's identity is not verified there. Contact support. |
INBOUND_NOT_CONFIGURED (503) |
Inbound email is not available on this deployment. GET reports it as available: false. |
PROVIDER_UNAVAILABLE (502) |
The email provider could not be reached. Retry. |
What you receive
The inbound.email webhook
{
"event": "inbound.email",
"data": {
"inbound_message_id": "inb_V1StGXR8_Z5jdHi6B-myT0aB",
"from": "jane@customer.example",
"from_name": "Jane Doe",
"envelope_from": "0100018f…@amazonses.com",
"to": "support@acme.com",
"subject": "Invoice question",
"attachments": [
{
"index": 0,
"filename": "invoice.pdf",
"content_type": "application/pdf",
"size": 48213,
"disposition": "attachment",
"content_id": null,
"downloadable": true
}
],
"verdicts": {
"spam": "PASS",
"virus": "PASS",
"spf": "PASS",
"dkim": "PASS",
"dmarc": "PASS"
}
},
"timestamp": "2026-10-05T12:00:00.000Z",
"tenant_id": null,
"delivery_id": "whd_01HN..."
}
The envelope is the usual one (events.md), scoped to
the domain's tenant. It carries no environment: there is no send behind it.
fromis the address in the message's From header — who the person sent it as.envelope_fromis the SMTPMAIL FROM(the Return-Path), which for mail sent through an email service is usually a bounce address of that service, not a person. Reply tofrom(or toReply-To, from the headers).- The payload carries no bodies and no download links: webhook payloads are stored and retried for days, longer than a download link lives. Fetch the message for its text, HTML and headers.
verdicts— see Spam, virus and authentication verdicts.- It fires once the message has been fully read, its body and attachments
included. For a few seconds before that,
GET /v1/inboundcan already list the message, withtextandhtmlstillnulland no attachments.
GET /v1/inbound/{id}
The whole message: from, to, subject, text, html, headers,
message_id_header, in_reply_to, references, conversation_id,
attachments (the same objects as in the webhook) and verdicts. GET /v1/inbound lists
messages with a preview; filter with channel=email, to, from, q and a
date range.
Bodies are decoded (transfer encodings and charsets) and each is kept up to 256 KB of text.
Attachments
curl https://www.sendoka.com/api/v1/inbound/inb_123/attachments/0 \
-H "Authorization: Bearer $SENDOKA_KEY"
{
"index": 0,
"filename": "invoice.pdf",
"content_type": "application/pdf",
"size": 48213,
"disposition": "attachment",
"content_id": null,
"downloadable": true,
"url": "https://…amazonaws.com/attachments/…?X-Amz-Signature=…",
"expires_at": "2026-10-05T12:10:00.000Z"
}
url is a direct download valid for 10 minutes; request a new one whenever
it expires. Fetch it without your Authorization header — the link carries
its own signature, and the storage host refuses a request that has both. Inline
images (disposition: "inline", with a content_id the HTML references as
cid:) are listed and downloadable the same way.
downloadable: false (and 404 ATTACHMENT_NOT_STORED from the download route)
means the attachment was seen but its contents were not kept: only the first
50 attachments of a message are stored, none are stored for a message whose
verdicts.virus is FAIL, and messages received before attachments were
stored list none at all.
Spam, virus and authentication verdicts
Every received message is scanned, and the results come with it, in the webhook
and in GET /v1/inbound/{id}:
"verdicts": { "spam": "PASS", "virus": "PASS", "spf": "PASS", "dkim": "GRAY", "dmarc": "PASS" }
Each value is PASS, FAIL, GRAY (no verdict could be reached),
PROCESSING_FAILED, or DISABLED, and null when no verdict was recorded
(messages received before verdicts were kept have verdicts: null).
A verdict does not stop the message reaching you. You decide what to do with
spam or a failed SPF, DKIM or DMARC check. The one exception is a virus: when
virus is FAIL, the message is stored and announced as usual, but its
attachments are listed with downloadable: false and their contents are not
kept.
Limits
- Message size: 40 MB, headers and all attachments included, after encoding — the receiving server's own limit. Larger messages are rejected at the MX and the sender gets a bounce.
- Attachments stored per message: 50. The rest are listed with
downloadable: false. - Body text kept: 256 KB each for
textandhtml. - Headers kept: 64 KB. A message whose header block is larger keeps
Message-ID,In-Reply-ToandReferencesfirst, then the other headers in order until the limit; the rest are left out ofheaders. Threading (conversation_id) is still read from the full header block, and a longreferencesvalue keeps its last 8 KB, which holds the ids it threads on. - Retention: received messages and their attachments are deleted after Sendoka's inbound retention period of 90 days. Erasing a tenant deletes its received messages at once, but the stored originals and attachments are not removed early: they expire on the same 90-day schedule. Until then they can no longer be reached through the API, because the messages that pointed at them are gone.
- Messages are scanned for spam and viruses. The verdicts are reported to you and do not block delivery, except that a message with a virus has no attachments stored (see above).