API v1.0
SendPigeon Documentation
Everything you need to send transactional emails. SDKs for Node.js, Python, Go, PHP and REST API.
Inbound Email
Receive emails on your domain via webhook or forwarding.
Paid plan required. Inbound email is available on Starter and Pro plans.
1. Add MX Record
Point your domain's MX record to SendPigeon to receive emails.
| Type | Priority | Value |
|---|---|---|
| MX | 10 | inbound.sendpigeon.dev |
2. Choose Mode
Configure how inbound emails are handled in your domain settings.
| Mode | Description |
|---|---|
| forward | Forward emails to another address |
| webhook | POST email data to your endpoint |
Webhook Payload
When an email arrives, we POST the following to your webhook URL:
Webhook Payloadjson
{ "event": "email.received", "timestamp": "2025-01-15T10:30:00Z", "data": { "id": "in_abc123", "from": "sender@example.com", "to": "hello@yourdomain.com", "subject": "Hello!", "text": "Plain text body...", "html": "<p>HTML body...</p>", "attachments": [ { "filename": "document.pdf", "contentType": "application/pdf", "size": 12345, "url": "https://..." } ], "rawUrl": "https://..." }}Attachment URLs are presigned and expire after 1 hour.
Verify Signature
Verify the webhook signature to ensure requests are from SendPigeon.
TypeScripttypescript
import { verifyInboundWebhook } from "sendpigeon";app.post("/inbound", async (req, res) => { const result = await verifyInboundWebhook({ payload: req.body, signature: req.headers["x-webhook-signature"], timestamp: req.headers["x-webhook-timestamp"], secret: process.env.INBOUND_WEBHOOK_SECRET, }); if (!result.valid) { return res.status(400).json({ error: result.error }); } const { from, to, subject, text, html, attachments } = result.payload.data; console.log(`Email from ${from}: ${subject}`); res.status(200).json({ received: true });});Webhook Headers
| Header | Description |
|---|---|
| X-Webhook-Signature | HMAC-SHA256 signature of the payload |
| X-Webhook-Timestamp | Unix timestamp when signature was created |
Manual Verification
If not using the SDK, verify signatures manually:
TypeScripttypescript
import crypto from "crypto";function verifySignature(payload, signature, timestamp, secret) { const data = `${timestamp}.${JSON.stringify(payload)}`; const expected = crypto .createHmac("sha256", secret) .update(data) .digest("hex"); // Timing-safe comparison return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) );}Rate limit: 100 emails/minute per domain. Retry: Failed webhook deliveries are retried 3 times with exponential backoff.