Webhooks Guide
Webhooks push new messages to your server the moment they arrive — no polling required. Register a URL and Mailto.Bot will deliver a JSON payload for each inbound message.
Last updated: March 25, 2026
Overview
Each mailbox can have one or more webhooks. When a message arrives, Mailto.Bot sends an HTTP POST to each registered URL with the full message payload as JSON.
- Delivery is near-instant — typically under 500 ms from message receipt.
- Failed deliveries are retried with exponential back-off (see Retry behaviour).
- Your endpoint must respond with a 2xx status within 10 seconds.
- Multiple webhooks on the same mailbox each receive their own delivery.
Registration
Register a webhook with POST /api/mailboxes/:name/webhooks. You can use HTTPS or HTTP (HTTP is allowed for local development).
curl -X POST \
https://mailto.bot/api/mailboxes/abc123/webhooks \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "url": "https://your-server.com/webhook" }'secret field is returned only once at registration time. Use it to verify that incoming webhook requests are genuinely from Mailto.Bot — see the verification section below.You can list or delete webhooks from the API Reference or the Dashboard.
Payload format
Mailto.Bot sends a Content-Type: application/json POST to your URL with the following body:
{
"event": "message.received",
"mailbox": "abc123",
"message": {
"id": "msg_01HXZ...",
"from": "sender@example.com",
"to": ["abc123@mailto.bot"],
"subject": "Hello from the webhook",
"text": "Plain-text body here.",
"html": "<p>Plain-text body here.</p>",
"attachments": [],
"receivedAt": "2026-04-15T20:00:00.000Z"
}
}| Field | Type | Description |
|---|---|---|
event | string | Always "message.received" currently. |
mailbox | string | Slug of the receiving mailbox. |
message.id | string | Unique message ID. |
message.from | string | Sender email address. |
message.to | string[] | Recipient addresses. |
message.subject | string | Subject line. |
message.text | string | Plain-text body (may be empty). |
message.html | string | HTML body (may be empty). |
message.attachments | object[] | Attachment metadata (filename, contentType, size, url). |
message.receivedAt | string | ISO 8601 receipt timestamp. |
Handling events
Your endpoint should acknowledge receipt immediately and process the event asynchronously. If processing takes more than 10 seconds, Mailto.Bot will consider the delivery a failure and retry.
// Express.js example
app.post('/webhook', express.json(), (req, res) => {
const { event, mailbox, message } = req.body;
if (event === 'message.received') {
console.log('New message in', mailbox, ':', message.subject);
// Process message...
}
// Always respond 2xx quickly
res.sendStatus(200);
});message.id field as an idempotency key to deduplicate deliveries.Retry behaviour
If your endpoint returns a non-2xx response or times out, Mailto.Bot retries the delivery with exponential back-off:
| Attempt | Delay after failure |
|---|---|
| 1st retry | 30 seconds |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| Final attempt | 6 hours — after this, delivery is abandoned |
Webhook failures are visible in the Dashboard under your mailbox settings. You can re-enable a paused webhook by editing it in the Dashboard.
Testing with ngrok
During development your server runs on localhost, which Mailto.Bot cannot reach. Use ngrok to expose a public tunnel to your local server.
# Install ngrok (https://ngrok.com) then:
ngrok http 3000
# ngrok outputs a public URL like:
# Forwarding https://abc123.ngrok.io -> http://localhost:3000
# Register that URL as your webhook:
curl -X POST \
https://mailto.bot/api/mailboxes/abc123/webhooks \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "url": "https://abc123.ngrok.io/webhook" }'The ngrok URL changes each time you restart. For persistent tunnels during active development, consider a fixed subdomain on a paid ngrok plan or an alternative like localtunnel.
Continue reading