Inbox
Receive mail through the API, wait for messages, and read codes, links, and attachments.
Create a receiving address through the API. Use it for application checks and email tests. This guide covers receiving only.
Use a full key for setup and deletion. The examples use a live key from your server environment:
export SENDTIER_API_KEY=st_live_REPLACE_MEReplace the example domain and IDs with your own values. Inbox mode comes from the key that creates it. Live keys see live inboxes. Test keys see test inboxes. An inbox's mode never changes.
Set up a receiving subdomain
Use a dedicated subdomain such as inbox.example.com. Never publish the Sendtier MX on your main domain. It takes over mail for every existing mailbox there, and that mail stops reaching your mailbox provider. Keep your main domain's MX records unchanged.
Add the subdomain with a full key:
curl https://api.sendtier.com/v1/domains \
-H "Authorization: Bearer $SENDTIER_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"name":"inbox.example.com"}'Save the returned id as your domain ID. Publish the sending records in records, as described in Domains and DNS. Then publish the MX record in receiving_mx on the same subdomain. Use its exact name, value, and priority. The records list omits this record while receiving is off, so read it from receiving_mx. Publish it before you enable receiving. Its status stays pending until you enable receiving. If the response has no receiving_mx, this deployment does not have receiving configured.
The Sendtier receiving MX must have a strictly lower priority number than every other MX record on this subdomain. A tie fails the check. Remove other MX records on the subdomain, or give them a higher number.
curl -X POST https://api.sendtier.com/v1/domains/dom_MZXW6YTBOI/verify \
-H "Authorization: Bearer $SENDTIER_API_KEY"The response's status must be verified. Receiving MX is optional for sending verification. To receive mail, enable receiving after publishing the MX record:
curl -X PATCH https://api.sendtier.com/v1/domains/dom_MZXW6YTBOI \
-H "Authorization: Bearer $SENDTIER_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"receiving_enabled":true}'This returns the updated domain with receiving_enabled: true. Enabling requires a verified domain and a passing receiving MX check. A failed check returns 422 domain_not_verified. If receiving is not configured, the API returns 503 unavailable with the message "Receiving is not configured." To turn receiving off, send {"receiving_enabled":false}. This needs no DNS check.
Create an inbox
Create an inbox on the receiving subdomain:
curl https://api.sendtier.com/v1/inboxes \
-H "Authorization: Bearer $SENDTIER_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"username":"signup","domain":"inbox.example.com","display_name":"Signup checks","retention_days":1}'The 201 response is an inbox object:
{
"id": "ibx_MZXW6YTBOI",
"address": "signup@inbox.example.com",
"username": "signup",
"domain": "inbox.example.com",
"display_name": "Signup checks",
"retention_days": 1,
"test_mode": false,
"created_at": "2026-10-16T10:00:00Z"
}Omit username to generate one. Use the domain name, not the domain ID, in this request. Each address belongs to one inbox across both modes.
A domain that is not verified, or has receiving off, returns 422 domain_receiving_disabled. An invalid username returns 422 invalid_inbox_username. A taken address returns 409 inbox_address_taken.
Start waiting, then send a test message
Start a wait before triggering your application's test email. Run this request in one terminal:
curl --get https://api.sendtier.com/v1/inboxes/ibx_MZXW6YTBOI/messages/wait \
-H "Authorization: Bearer $SENDTIER_API_KEY" \
--data-urlencode 'timeout=30' \
--data-urlencode 'subject_contains=Verify'Once the wait request is active, send a message to signup@inbox.example.com from your application or mailbox provider. Use a subject containing Verify. A Sendtier test key does not deliver mail. Do not use it to send this message.
Without after, the wait accepts only messages received after that request starts. It returns message metadata with 200, or an empty 204 after the timeout. The timeout is 1–30 seconds. Set your HTTP client timeout above the wait timeout. Metadata includes id, thread_id, authentication, and extracted; it does not include bodies.
Save the returned message id. Use it as after on the next wait. After a 204, repeat the request with the same after value:
curl --get https://api.sendtier.com/v1/inboxes/ibx_MZXW6YTBOI/messages/wait \
-H "Authorization: Bearer $SENDTIER_API_KEY" \
--data-urlencode 'after=msg_MZXW6YTBOI' \
--data-urlencode 'timeout=30' \
--data-urlencode 'subject_contains=Verify'after also accepts a next_cursor from List messages. A wait returns the oldest matching message after the after position, ordered by received time and message ID. Lists run newest first. To resume after a list, pass the id of the newest message you listed. If the first wait has no after and returns 204, list messages before the next wait to check the gap. If the after message is deleted or expired, the wait returns 400 invalid_request. Start again without after.
A late retried delivery can arrive with an earlier received time than your after message. The wait skips it. A wait does not guarantee that you see every delivery. List messages again, without a cursor, and compare message IDs to find missed mail. Lists return data and may return next_cursor.
The optional from filter matches an exact sender address, ignoring case. subject_contains matches a literal substring, ignoring case. Too many concurrent waits return 429; retry with backoff.
Read a message, code, and link
Retrieve the message to read its text and HTML:
curl https://api.sendtier.com/v1/inboxes/ibx_MZXW6YTBOI/messages/msg_MZXW6YTBOI \
-H "Authorization: Bearer $SENDTIER_API_KEY"For a message containing a verification code, link, and attachment, the response has this shape:
{
"id": "msg_MZXW6YTBOI",
"inbox_id": "ibx_MZXW6YTBOI",
"thread_id": "thr_MZXW6YTBOI",
"direction": "inbound",
"from": {"address": "hello@example.com"},
"to": ["signup@inbox.example.com"],
"cc": [],
"subject": "Verify your account",
"preview": "Your verification code is 123456.",
"size_bytes": 2048,
"attachments": [
{"index": 0, "filename": "welcome.txt", "content_type": "text/plain", "size": 7, "inline": false}
],
"received_at": "2026-10-16T10:01:00Z",
"expires_at": "2026-10-17T10:01:00Z",
"text": "Your verification code is 123456. Verify at https://example.com/verify?token=example",
"html": "",
"authentication": {"spf": "pass", "dkim": "pass", "dmarc": "pass"},
"extracted": {
"version": 1,
"codes": [{"value": "123456", "raw": "123456", "confidence": 90}],
"links": [{"url": "https://example.com/verify?token=example", "kind": "verification"}]
}
}Read codes from extracted.codes[].value and links from extracted.links[].url. Both arrays can be empty. Check the sender and message before using extracted values. Extraction does not prove that a message or link is trusted.
Use List threads with inbox_id to find conversations. Retrieve a thread returns message metadata, oldest first. Use its next_cursor as after for the next page. A thread appears only while it has a message that is neither deleted nor expired. Otherwise, retrieving it returns 404 not_found.
Download raw mail and attachments
Download the raw message as RFC 5322 bytes:
curl https://api.sendtier.com/v1/inboxes/ibx_MZXW6YTBOI/messages/msg_MZXW6YTBOI/raw \
-H "Authorization: Bearer $SENDTIER_API_KEY" \
--output message.emlDownload an attachment using its zero-based index from attachments:
curl https://api.sendtier.com/v1/inboxes/ibx_MZXW6YTBOI/messages/msg_MZXW6YTBOI/attachments/0 \
-H "Authorization: Bearer $SENDTIER_API_KEY" \
--output welcome.txtThese responses contain bytes, not JSON or public storage links. The API sends them as downloads. Message bodies and attachments are parsed from stored raw mail. A full raw-read limit returns 503; retry with backoff.
Create an inbox-scoped key
Keep the full key on your server. Create a separate key for a reader that needs only this inbox:
curl https://api.sendtier.com/v1/api-keys \
-H "Authorization: Bearer $SENDTIER_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"name":"Signup reader","mode":"live","scope":"inbox","inbox_id":"ibx_MZXW6YTBOI"}'Save the 201 response's key now. The full secret is returned only once. The key's mode must match the inbox. Use that secret as the bearer for reading this inbox, waiting, reading its messages, downloading raw mail or attachments, and reading its threads.
An inbox key is GET-only. It cannot list all inboxes, change settings, delete resources, or send mail. It cannot read another inbox. The binding uses the inbox ID. Reusing a deleted inbox's address does not transfer access.
Delete messages and inboxes
Use the full key to delete a message:
curl -X DELETE https://api.sendtier.com/v1/inboxes/ibx_MZXW6YTBOI/messages/msg_MZXW6YTBOI \
-H "Authorization: Bearer $SENDTIER_API_KEY"The response is 204. The message becomes unreadable and its personal metadata is cleared. Raw deletion follows the database commit. Cleanup retries a failed storage delete. Pending webhook deliveries fail with message_deleted. A delivery already in progress may still send its original metadata once.
Delete the inbox when the test is complete:
curl -X DELETE https://api.sendtier.com/v1/inboxes/ibx_MZXW6YTBOI \
-H "Authorization: Bearer $SENDTIER_API_KEY"This returns 204 and frees the address. Cleanup removes the old inbox's messages and raw objects. A new inbox at that address has a new ID. Deleting the inbox does not revoke its key; revoke the key separately.
Retention
Set retention_days from 1 day through your plan maximum. It defaults to that maximum: Free 7 days, Pro 30 days, Scale 90 days. Pro and Scale are planned, not on sale. Update an inbox to change its display name or retention setting.
Each message has an expires_at. Expired and deleted messages are not readable or downloadable. With inbound storage configured, cron cleans up at startup and every hour. Failed raw deletes retry later. See Data retention for cleanup and tombstone rules.
Sources: public request and response contracts (openapi/openapi.yaml), receiving design (docs/adr/0038-inbound-mail-and-inboxes.md), DNS checks (internal/dns/check.go), inbox scope (internal/api/inbox_scope.go), inbox cleanup (internal/cron/inbox.go).