Inbound
Receive replies via webhook
When mail arrives at support@yourdomain.com (or any address at a domain you own), SendBolt can POST the raw RFC822 message to your application with an HMAC-signed header so you can verify origin.
Set up
- Confirm an MX record is published for your domain (see DNS)
- Configure the webhook URL via the settings endpoint or UI
- Implement an HMAC-verifying receiver in your app
Configure
curl -X PATCH "$SENDBOLT_API_URL/api/v1/settings/inbound-webhook" \
-H "Authorization: Bearer $SENDBOLT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://acme.com/api/inbound-mail",
"enabled": true
}'
# Response — copy "secret" once, never shown again:
# {
# "webhook_url": "https://acme.com/api/inbound-mail",
# "enabled": true,
# "secret": "ihw_a1b2c3d4...",
# "secret_revealed_once": true
# }Store the secret in your app's env (e.g. SB_INBOUND_SECRET). If you lose it, rotate via ?rotate_secret=true on the PATCH.
Receive
// app/api/inbound-mail/route.ts
import { createHmac, timingSafeEqual } from "node:crypto";
import { simpleParser } from "mailparser";
export async function POST(req: Request) {
const sig = req.headers.get("x-sb-inbound-sig") ?? "";
const tenant = req.headers.get("x-sb-inbound-tenant");
const recipient = req.headers.get("x-sb-inbound-recipient");
const body = await req.text();
// Verify HMAC
const expected = "hmac-sha256=" + createHmac("sha256", process.env.SB_INBOUND_SECRET!)
.update(body)
.digest("hex");
if (sig.length !== expected.length || !timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return new Response("invalid signature", { status: 401 });
}
// Parse RFC822
const mail = await simpleParser(body);
// Route to your helpdesk / queue / DB
await db.supportTickets.insert({
fromEmail: mail.from?.value[0]?.address,
fromName: mail.from?.value[0]?.name,
toAddress: recipient,
tenantId: tenant,
subject: mail.subject,
bodyText: mail.text,
bodyHTML: mail.html,
receivedAt: new Date(),
});
return new Response("ok");
}HTTP contract
- Method: POST
- Content-Type:
message/rfc822 - Body: raw RFC822 bytes (the full email including headers)
- Headers:
X-SB-Inbound-Sig: hmac-sha256=<hex>— HMAC of the body using your shared secretX-SB-Inbound-Tenant: <tenant_id>X-SB-Inbound-Recipient: <the-to-address-on-the-envelope>
Response expectations
- Return 2xx within 10 seconds — SendBolt accepts on queue
- Return 4xx (non-429) — message dead-lettered immediately, no retry
- Return 429 + Retry-After — SendBolt defers per your header
- Return 5xx or timeout — retried with backoff 30s→24h across 7 attempts before dead-lettering
Dead-lettered messages are visible at GET /api/v1/admin/tenants/{id}/inbound-webhook-dlq for super-admin review.
Test it
curl -X POST "$SENDBOLT_API_URL/api/v1/settings/inbound-webhook/test-send" \
-H "Authorization: Bearer $SENDBOLT_API_KEY"
# This sends a synthetic "Hello from SendBolt" message to your configured
# webhook URL. If your endpoint returns 2xx, the test passes and you'll see
# the round-trip status + your HTTP response code echoed back.Common pitfalls
- Don't parse the recipient from the email body — use the
X-SB-Inbound-Recipientheader. BCC'd recipients aren't in the visible To/Cc headers - Don't auto-reply to every inbound mail — bounce loops + vacation auto-responders create message storms. Detect
Auto-Submitted: auto-repliedheaders and skip those - Don't trust the From: header alone — receiver-side SPF/DKIM/DMARC are NOT enforced by the inbound parser as of today. Treat inbound mail as untrusted user input
Alternative: if you want a full Gmail-like inbox UI for replies rather than a webhook, see Workspace mailboxes.