Inbound Webhooks
Inbound Webhooks
Postscale can parse incoming emails and deliver them to your application via webhooks. This enables powerful use cases like support ticket systems, reply detection, and email-based workflows.
Setup
- Configure your domain's MX records to point to Postscale.
- From the dashboard, open Webhooks, click Add Webhook, point it at
your endpoint URL, and check
email.received(any other events you want are also fine — one webhook can subscribe to many). - Copy the signing secret (
whsec_…) shown in the dialog. It is only displayed once; save it in your secrets manager.
For root-domain inbound processing, the domain must be configured as inbound,
both, or alias, and its MX record must point to mx.postscale.io. If you
only send outbound mail through Postscale, use an outbound domain instead and
leave your mailbox provider's MX records unchanged.
You can also create the webhook via API:
curl -X POST https://api.postscale.io/v1/webhooks \
-H "Authorization: Bearer ps_live_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/webhooks/postscale-inbound",
"events": ["email.received"]
}'
The response body contains the webhook record and the plaintext secret.
Save it then — GET /v1/webhooks will not return it again.
Webhook Payload
When an email arrives, Postscale POSTs a JSON body like this to your endpoint:
{
"event": "email.received",
"timestamp": "2026-04-25T10:30:00Z",
"email_id": "0c1f2a3b-…",
"message_id": "<unique-id@example.com>",
"from": "customer@example.com",
"to": "support@yourapp.com",
"subject": "Help with my order",
"text": "Hi, I need help with order #12345...",
"html": "<p>Hi, I need help with order #12345...</p>",
"headers": [
{ "name": "Message-ID", "value": "<unique-id@example.com>" },
{ "name": "In-Reply-To", "value": "<previous-id@yourapp.com>" }
],
"attachments": [
{
"id": "5b7d9c0e-…",
"filename": "screenshot.png",
"content_type": "image/png",
"size": 45678,
"url": "https://api.postscale.io/v1/inbound-emails/0c1f2a3b-…/attachments/5b7d9c0e-…/download"
}
],
"spf": "pass",
"dkim": "pass"
}
Each request also carries metadata headers:
X-Postscale-Event: email.received
X-Postscale-Delivery-ID: <uuid> (unique per attempt — use for dedup)
X-Postscale-Attempt: 1 (1-indexed; bumps on retry)
X-Postscale-Signature: t=…,v1=… (verification — see below)
Verifying Webhooks
Every request signed by Postscale carries an X-Postscale-Signature header
of the form t=<unix>,v1=<hex_hmac_sha256>. Verify it on every request to
ensure the delivery came from Postscale and was not tampered with or
replayed.
The full algorithm, t= / v1= format details, dual-signature handling
during a rotation grace window,
and Node + Python reference verifiers live in the main
Webhooks doc — that page is shared
across inbound and outbound webhooks because the verification protocol is
identical.
Use the In-Reply-To and References headers to match replies to
original messages and maintain conversation threads.
Each attachment exposes an authenticated Postscale download url. Fetch it
with your normal API credentials, then copy the file into your own storage if
your workflow needs retention outside Postscale's retention window. Where
application-side attachment encryption is enabled, this API path also handles
decryption after authorization.