Sending

Inbound email

Receive mail on your domains, with parsed messages pushed to your webhook and a pull API to back it up.

Inbound receiving and Sendara Mail are availability-controlled features. If either feature is not enabled on the Sendara deployment serving your account, its API routes return 404. Contact Sendara before building a production workflow that depends on them.
Inbound is opt-in per domain. Enable it only on the domains you want to receive on. Already using another service for a domain's mail? Leave it off there and nothing changes. Sending is never affected.

Publish the MX record that Sendara gives you. It points at the Amazon SES receiving endpoint for the region, inbound-smtp.<region>.amazonaws.com, at priority 10. SES then receives every email to that domain and hands it to Sendara. Sendara parses it into fields (from, to, subject, text, HTML, attachments) and delivers it to you in two ways: a real-time email.received webhook, and a GET /v1/inbound pull API.

Enable a domain

Receiving is enabled on a verified domain. Enabling returns the MX record to publish at your DNS provider.

An MX record routes all mail for that domain to Sendara. If you already use email (Google Workspace, etc.) on your main domain, enable a subdomain like inbound.yourdomain.com instead so your existing mail is untouched.
curl -X POST https://api.sendara.dev/v1/domains/dom_abc123/inbound \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": true }'
enabledbooleanRequired
Turn receiving on or off for the domain. Enabling returns the MX record to publish.
{ "enabled": true, "mx_record": { "type": "MX", "priority": "10", "value": "inbound-smtp.us-east-1.amazonaws.com" } }

Prefer zero setup? Every account also has a hosted test inbox at <account_id>@inbound.sendara.dev. Email it from anywhere to see receiving work with no DNS changes.

Mail inboxes

Sendara Mail can host multiple inbox addresses on the same verified domain. Create addresses like [email protected], [email protected], and [email protected], then filter the Mail app, threads API, and search API by that inbox. This is separate from routing rules: inboxes organize mail inside Sendara, while routes forward matched mail to another system.

Creating an inbox does not disable catch-all receiving for the domain. Messages to configured addresses get an inbox_id; messages to other addresses on the same domain still land in the account-level mailbox.
# create [email protected]
curl https://api.sendara.dev/v1/mail/inboxes \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "domain": "mail.acme.com", "local_part": "support" }'

# list configured inboxes
curl https://api.sendara.dev/v1/mail/inboxes \
  -H "Authorization: Bearer sk_live_xxx"

# read one inbox view
curl "https://api.sendara.dev/v1/mail/threads?inbox=support%40mail.acme.com" \
  -H "Authorization: Bearer sk_live_xxx"

# search inside that inbox
curl "https://api.sendara.dev/v1/mail/search?q=invoice&inbox=support%40mail.acme.com" \
  -H "Authorization: Bearer sk_live_xxx"
addressstringOptional
Full inbox address, such as [email protected]. Alternative to domain plus local_part.
domainstringOptional
Verified, inbound-enabled domain. Required when address is omitted.
local_partstringOptional
Mailbox name before @. Required when address is omitted.
namestringOptional
Optional display label.

Replies from a thread default to the address that received the latest inbound message when that domain is verified for sending. You can also pass from explicitly to POST /v1/mail/compose or POST /v1/mail/threads/{id}/reply.

Search and export mail

Mail search accepts normal text plus operators for the cases where generic inbox search usually falls short. Use from:, to:, subject:, has:attachment, after:YYYY-MM-DD, and before:YYYY-MM-DD. The same filters work on exports, so users can take a precise slice of their mailbox with them.

# search a single role inbox
curl "https://api.sendara.dev/v1/mail/search?q=from%3Aalice%40example.com%20has%3Aattachment&inbox=support%40mail.acme.com" \
  -H "Authorization: Bearer sk_live_xxx"

# export that same slice as JSON
curl "https://api.sendara.dev/v1/mail/export?format=json&q=from%3Aalice%40example.com%20has%3Aattachment&inbox=support%40mail.acme.com" \
  -H "Authorization: Bearer sk_live_xxx" \
  -o support-export.json

# export as mbox for mailbox import tools
curl "https://api.sendara.dev/v1/mail/export?format=mbox&inbox=support%40mail.acme.com" \
  -H "Authorization: Bearer sk_live_xxx" \
  -o support.mbox

The email.received webhook

Subscribe a webhook to the email.received event. Each received message is delivered as a signed POST (verified exactly like every other Sendara webhook). Like every event, it arrives wrapped in the standard envelope. Top-level keys are event_id / event_type, and the inbound email lives under payload. The example below is abbreviated. The full payload also carries cc, headers, size_bytes, thread_id, inbox_id, rfc_message_id, in_reply_to, references_ids, normalized_subject, and an s3_key on each attachment.

{
  "event_id": "evt_inb_9f21",
  "event_type": "email.received",
  "message_id": "inb_9f21",
  "account_id": "acc_abc123",
  "payload": {
    "id": "inb_9f21",
    "domain": "inbound.yourdomain.com",
    "recipient": "[email protected]",
    "inbox_id": "inbox_a1b2c3",
    "from": "Alice <[email protected]>",
    "to": ["[email protected]"],
    "cc": [],
    "subject": "Need help with my order",
    "text": "Hi, my order hasn't arrived…",
    "html": "<p>Hi, my order hasn't arrived…</p>",
    "spf_verdict": "PASS",
    "dkim_verdict": "PASS",
    "dmarc_verdict": "PASS",
    "spam_verdict": "PASS",
    "virus_verdict": "PASS",
    "size_bytes": 84992,
    "thread_id": "thr_5c10",
    "attachments": [{ "filename": "receipt.pdf", "content_type": "application/pdf", "size": 84211, "s3_key": "inbound/inb_9f21/att/0" }],
    "received_at": "2026-06-17T10:02:00Z"
  },
  "occurred_at": "2026-06-17T10:02:00Z",
  "created_at": "2026-06-17T10:02:01Z"
}
Amazon SES scans every inbound message. Sendara reads the SES verdicts off the receipt and puts them on each event as spf_verdict, dkim_verdict, dmarc_verdict, spam_verdict and virus_verdict. Sendara drops a message whose virus_verdict is FAIL, so it never reaches you. Sendara does not drop a message on a spam verdict. It delivers the message and gives you the verdict to act on.

Pull API

List and fetch received mail over the API. This is useful for backfill or if you would rather poll than run a webhook.

# list recent inbound emails (bodies omitted)
curl https://api.sendara.dev/v1/inbound \
  -H "Authorization: Bearer sk_live_xxx"

# fetch one with full body + attachments metadata
curl https://api.sendara.dev/v1/inbound/inb_9f21 \
  -H "Authorization: Bearer sk_live_xxx"

# download an attachment by index
curl https://api.sendara.dev/v1/inbound/inb_9f21/attachments/0 \
  -H "Authorization: Bearer sk_live_xxx" -o receipt.pdf

# fetch the original, unparsed RFC 822 message
curl https://api.sendara.dev/v1/inbound/inb_9f21/raw \
  -H "Authorization: Bearer sk_live_xxx" -o message.eml

The routing rules

By default Sendara fires email.received to your subscribed webhooks for every received message. A routing rule forwards one address to its own destination. The destination is a webhook (support@ to your ticketing system) or an email address (a real inbox). Set destination_type to webhook or to email. You create, list, update (PUT /v1/inbound/routes/{id}) and delete a route over the API.

# forward support@ to a webhook (durable: signed, retried, logged)
curl https://api.sendara.dev/v1/inbound/routes \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "inbound.yourdomain.com",
    "match_prefix": "support",
    "destination_type": "webhook",
    "forward_url": "https://app.yourcompany.com/hooks/inbound"
  }'

# forward everything else to a real inbox
curl https://api.sendara.dev/v1/inbound/routes \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "inbound.yourdomain.com",
    "match_prefix": "*",
    "destination_type": "email",
    "forward_address": "[email protected]"
  }'
domainstringRequired
A verified, inbound-enabled domain you own.
match_prefixstringOptional
The local-part to match (e.g. support), or * for every address. Defaults to *.
destination_typestringOptional
webhook (default) to POST the message to a URL, or email to forward it to another address.
forward_urlstringOptional
Required for destination_type webhook. An https endpoint that receives the matched mail, delivered durably (signed, retried, with delivery history) exactly like any Sendara webhook. The response includes a signing_secret, shown once.
forward_addressstringOptional
Required for destination_type email. Sendara forwards the matched mail to this address, which is a real inbox. A reply goes to the original sender.

Sendara delivers a webhook route forward through the same durable path as every other Sendara webhook. Sendara retries it with a growing delay, records it in your delivery history, and signs it. The signing scheme is the same as the scheme for an email.received subscription. It is a bare hex Sendara-Signature header and a Sendara-Timestamp header, over the same envelope. The SDK verifyWebhook helper therefore works for a route forward and for a subscription.

import { verifyWebhook, WebhookVerificationError } from "sendara";

// Express handler. The raw request body is required to verify the signature.
app.post("/hooks/inbound", express.raw({ type: "*/*" }), (req, res) => {
  try {
    // Verifies the Sendara-Signature / Sendara-Timestamp headers and returns the
    // parsed event. Works for email.received subscriptions AND route forwards.
    const event = verifyWebhook(req.body, req.headers, process.env.SENDARA_WEBHOOK_SECRET!);

    if (event.event_type === "email.received") {
      const email = event.payload; // parsed inbound email: from, subject, text, html, thread_id…
      // …append it to a conversation thread keyed by email.thread_id / in_reply_to…
    }
    res.sendStatus(200);
  } catch (err) {
    if (err instanceof WebhookVerificationError) return res.status(400).send("bad signature");
    throw err;
  }
});
To verify the signature yourself, recompute HMAC-SHA256(signing_secret, "<Sendara-Timestamp>." + rawBody). Hex-encode the result. Then compare it to Sendara-Signature in constant time. The steps are the same for a subscription and for a route forward.

Common questions

Will the MX record break my existing email?
It can. An MX record routes all mail for that domain to Sendara. Enable a subdomain such as inbound.yourdomain.com instead. Your existing mail at the apex then stays with your current provider.
Can I try receiving before I change any DNS?
Yes. Every account has a hosted test inbox at <account_id>@inbound.sendara.dev. Send mail to it from anywhere, and the message arrives on the same webhook and the same pull API.
Does Sendara filter spam and viruses?
Amazon SES scans every inbound message. Sendara puts the verdicts on the event as spf_verdict, dkim_verdict, dmarc_verdict, spam_verdict and virus_verdict. Sendara drops a message whose virus_verdict is FAIL. Sendara delivers a message with a spam verdict, and gives you the verdict to act on.
What is the difference between an inbox and a route?
An inbox organizes mail inside Sendara, and it puts an inbox_id on the message. A route forwards a matched address to a webhook or to another email address. When you create an inbox, the domain still accepts mail sent to every other address.
Can I read the original message?
Yes. Call GET /v1/inbound/{id}/raw for the unparsed RFC 822 message. Call GET /v1/inbound/{id}/attachments/{index} for one attachment. Both routes take a read key.