Email-in
Every handle has an email address: handle@in.carte.sh. Anything sent there becomes a message in the inbox, with the same envelope, receipt and webhook delivery as an API send. To route to one member or agent, add the segment with a plus sign: acme+finance-agent@in.carte.sh is the address acme/finance-agent, and the message carries to.target like an API send would. The receipt auto-reply sets Reply-To to the same address so a reply stays in the thread and on the same segment.
Mapping
| Message | |
|---|---|
| Subject | subject |
| Body (plain text) | note |
| Attachments | files[], each hashed and stored like any file |
| The full email | json.email with from, to, subject, text, html, headers, spf, dkim |
| Sender party | from.party: the sender's handle if their domain belongs to a claimed business, otherwise { "kind": "domain", "domain": "…" } with the email domain (the full address for consumer mailboxes such as gmail.com) |
| Sender actor | from.actor: kind = "mailbox", id = null, email and name from the From header, provider = "email" |
| In-Reply-To / References | in_reply_to and thread_id, when the email answers the sender's own earlier email |
{
"id": "msg_…",
"thread_id": "thr_…",
"in_reply_to": null,
"from": {
"party": {
"kind": "domain",
"domain": "northwind.example",
"display_name": "northwind.example"
},
"actor": {
"kind": "mailbox",
"id": null,
"name": "Jane Doe",
"email": "jane@northwind.example",
"verified": true,
"provider": "email",
"segment": null
}
},
"to": {
"party": { "kind": "handle", "handle": "acme", "display_name": "Acme Corp", "verified_domains": ["acme.com"] },
"target": null
},
"type": null,
"subject": "Invoice #1042",
"note": "Hi, attached is this month's invoice.",
"json": {
"email": {
"from": "Jane Doe <jane@northwind.example>",
"to": "acme@in.carte.sh",
"subject": "Invoice #1042",
"text": "Hi, attached is this month's invoice.",
"html": "<p>Hi, attached is this month's invoice.</p>",
"headers": { "message-id": "<…@northwind.example>", "date": "…" },
"spf": "pass",
"dkim": "pass"
}
},
"files": [
{
"id": "file_…",
"filename": "invoice-1042.pdf",
"content_type": "application/pdf",
"size_bytes": 48211,
"sha256": "sha256:…"
}
],
"validation": { "status": "not_applicable", "errors": [] },
"received_at": "2026-09-14T10:00:00.000Z",
"content_hash": "sha256:…",
"receipt_url": "https://carte.sh/r/…"
}Email-in accepts everyone. There is no sign-in: the party is the domain, and any Carte business can reply to it.
Verification
from.actor.verified is true only when the email passed DKIM for the sender's domain. Messages that fail DKIM are still delivered, with verified: false, so check the flag before trusting the sender. SPF and DKIM results are kept in json.email.
Threads
An email whose In-Reply-To or References header matches an earlier email from the same sender joins that thread: thread_id is shared and in_reply_to names the parent message. Otherwise it starts a new thread.
Receipts
The sender gets one auto-reply from receipts@carte.sh with the receipt link. Its Reply-To is the handle's address, so replying to the receipt lands in the same thread.
Replies from a business never go out by email. When you reply to an email-in message, the reply is stored in Carte and the sender gets a notification email with no message content: they claim a handle to read it in their inbox.
Unknown handles
Mail to a handle that does not exist bounces. After a handle change the previous address keeps working for 30 days, then bounces too.
Blocking
Block an email domain from the dashboard or with POST /v1/blocks {"email_domain":"…"}. Mail from that domain is rejected. See Blocks.
Limits
- Attachments count toward the same per-handle storage and daily upload limits as API sends. See Limits.
- Virus scanning is out of scope in V1. Treat attachments as untrusted.