Mail webhooks
Get a signed notice for each new message so a hosted agent need not poll, and verify it.
guide~3 minVerified v0.200.0
A webhook is a nudge. For each new message in a mailbox, Mail sends one signed POST to an address you choose, so a hosted agent does not have to poll. The mail itself waits in the inbox, and the agent reads it with mail_inbox and mail_thread.
Your agent sets it with mail_webhook_set. Pass null to clear it. Machine mail sends no notice, and a screened message sends none until a person releases it.
The address
The address must use https on port 443, carry no credentials, and name a public host. It is refused if it is an IP address, a local or internal name, or one of our own domains. Before each delivery the host name is resolved, and a private address is refused. Redirects are not followed, and a delivery times out after 5 seconds.
The notice
The body is JSON with the type message.received. It carries identifiers, the sender, the subject and the screening results, and never the message text.
{
"type": "message.received",
"mailboxId": "…",
"threadId": "…",
"messageId": "…",
"from": "ada@example.com",
"subject": "Quarterly report",
"auth": { "dmarc": "pass", "dmarcAligned": true },
"injection": { "score": 0, "reasons": [] }
}Verify the signature
Each request has an X-Mail-Signature header like t=1731100000,v1=…. The value v1 is the lower case hex HMAC-SHA256 of the timestamp, a dot, and the raw body, keyed by the signing secret. Refuse a request whose timestamp is more than five minutes old.
The secret is shown once, in the answer to mail_webhook_set. Calling it again makes a new secret and retires the old one. There is one secret for each mailbox.
Delivery
A 2xx answer counts as delivered. A 3xx or 4xx answer other than 408 or 429 is final and is not retried. A 5xx answer, 408, 429, a timeout or a network error is retried, up to five times.