Receive webhooks
Point CamelMailer at your URL from the dashboard, then run a receiver that answers fast, verifies the signature, and does its work asynchronously.
1. Add the endpoint in the dashboard
Open your mail server and go to Server → Webhooks, then choose New webhook. Give it a name, paste the URL that receives the callback, and pick the events it should get. Leaving every event unchecked subscribes to all of them. No API call is involved.
- Events. CamelMailer fires
MessageSent,MessageDelayed,MessageDeliveryFailed, andMessageHeld. The Webhooks reference explains what each one means and shows the payload. - Signing is on by default, so every delivery arrives with an RSA signature you verify below. Keep it on.
- Custom headers ride on every request, which is where a shared
Authorizationbearer token goes if your receiver expects one.
After saving, use Send test on the webhook row to deliver one sample payload straight away, with your headers and signature, marked test: true. The Enabled toggle pauses and resumes delivery while keeping the configuration.
2. A receiver that answers fast
app.post('/hooks/camelmailer', express.raw({ type: 'application/json' }), (req, res) => { // 1. verify the signature against the raw body (below) // 2. enqueue for processing, and do NOT do the work here queue.push(JSON.parse(req.body)); res.status(200).end(); });
Failed deliveries are retried with backoff, so a handler that takes seconds or 500s under load creates duplicate work for itself. Accept, enqueue, return.
3. Verify the signature
Deliveries are RSA-signed with the installation signing key, the same key CamelMailer uses for DKIM. Export its public half once and hand it to your receiver:
openssl rsa -in /path/to/signing.key -pubout -out camelmailer-webhooks.pub
Then check every request against the exact bytes you received, before you parse them:
const verifier = crypto.createVerify('RSA-SHA256'); verifier.update(rawBody); const ok = verifier.verify(publicKeyPem, signatureFromHeader, 'base64'); if (!ok) return res.status(400).end();
- Verify against the raw request body, because re-encoding the parsed JSON changes the bytes and breaks the signature.
- The signature arrives in
X-CamelMailer-Signatureas standard base64. Reject unsigned or badly signed requests loudly (400 plus an alert).
4. Process idempotently
Retries mean at-least-once delivery. The per-delivery uuid is stable across retries of one event, so key your processing on it and a replay becomes a no-op.
Debugging
- The dashboard's webhook request log (Server → Webhooks → Request log) shows every attempt with its request and response. Start there before your own logs.
- Local development: expose your receiver with a tunnel (ngrok, cloudflared) and point a webhook at it, then flip its Enabled toggle off afterwards.
Same endpoint over the API
To script it instead, create the same webhook through the management API. The dashboard and the API write the identical resource.
curl -X POST \
https://mail-admin.example.com/api/v2/admin/organizations/acme/servers/production/webhooks \
-H "X-Admin-API-Key: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "delivery-events",
"url": "https://app.example.com/hooks/camelmailer",
"events": ["MessageSent", "MessageDeliveryFailed"],
"sign": true
}'