Process inbound email
Configure your domain to deliver inbound email to Mailpipe, then read messages through the API or receive real-time webhook notifications as mail arrives.
To receive email, point your domain's MX record at Mailpipe's inbound mail server. MX records tell the internet where to deliver email addressed to your domain.
Type: MX Host: @ (root domain, e.g. example.com) Priority: 10 Value: mx.mailpipe.dev # If you also want a subdomain to receive mail (e.g. mail.example.com): Type: MX Host: mail Priority: 10 Value: mx.mailpipe.dev
You can verify that the record has propagated using dig or an online MX lookup tool:
dig MX example.com +short # Expected output: 10 mx.mailpipe.dev.
Remove conflicting MX records
If your domain already has MX records pointing at another provider (e.g., Google Workspace, Microsoft 365), remove or replace them. Multiple conflicting MX providers will cause unpredictable delivery.
A Mailpipe mailbox is an email address that stores inbound messages. Once your MX record points at mx.mailpipe.dev, create a mailbox for each address you want to receive mail at.
support, hello, billing)curl -X POST "https://api.mailpipe.dev/v1/mailboxes" \
-H "Authorization: Bearer mp_live_your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"address": "support",
"domain_id": "dom_xxxxxxxxxxxxxxxx",
"display_name": "Support Team",
"forward_to_webhook": true
}'{
"id": "mbx_01hx8yq3vdkj1p4nze7rbc56wf",
"address": "support@example.com",
"display_name": "Support Team",
"forward_to_webhook": true,
"created_at": "2025-01-15T10:30:00.000Z"
}Mailpipe accepts mail for any mailbox you create. Mail delivered to an address that has no mailbox is rejected at the SMTP level with a 550 No such user response.
Webhooks let Mailpipe push a notification to your server the moment a message is received, eliminating the need to poll for new mail. Configure a webhook endpoint in the dashboard or via the API.
curl -X POST "https://api.mailpipe.dev/v1/webhooks" \
-H "Authorization: Bearer mp_live_your-api-key-here" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/api/webhooks/mailpipe",
"events": ["message.received", "message.bounced"],
"mailbox_ids": ["mbx_01hx8yq3vdkj1p4nze7rbc56wf"]
}'When a message arrives, Mailpipe sends a signed POST request to your endpoint with the following payload:
{
"id": "evt_01hx9abc123",
"event": "message.received",
"created_at": "2025-01-15T10:30:00.000Z",
"data": {
"id": "msg_01hx8yq3vdkj1p4nze7rbc56wf",
"mailbox_id": "mbx_01hx8yq3vdkj1p4nze7rbc56wf",
"mailbox_address": "support@example.com",
"from": "customer@example.com",
"to": ["support@example.com"],
"subject": "Need help with my order",
"textBody": "Hello, I have a question about order #12345...",
"htmlBody": "<p>Hello, I have a question...</p>",
"attachments": [],
"headers": {
"message-id": "<abc123@example.com>",
"date": "Mon, 15 Jan 2025 10:30:00 +0000"
},
"received_at": "2025-01-15T10:30:00.000Z"
}
}Every webhook request includes an X-Mailpipe-Signature header. Verify this signature to confirm the request genuinely came from Mailpipe:
import crypto from 'crypto';
export function verifyWebhook(
payload: string, // raw request body as string
signature: string, // X-Mailpipe-Signature header value
secret: string // MAILPIPE_WEBHOOK_SECRET env var
): boolean {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Next.js App Router example
export async function POST(req: Request) {
const payload = await req.text();
const sig = req.headers.get('x-mailpipe-signature') ?? '';
if (!verifyWebhook(payload, sig, process.env.MAILPIPE_WEBHOOK_SECRET!)) {
return new Response('Unauthorized', { status: 401 });
}
const event = JSON.parse(payload);
// Handle event.event === 'message.received' ...
return new Response('OK', { status: 200 });
}Your endpoint must return a 2xx response within 10 seconds. Failed deliveries are retried up to 5 times with exponential back-off. See the Webhooks guide for full retry semantics.
All inbound messages are stored in Mailpipe's Supabase-backed database and accessible via the REST API regardless of whether webhooks are configured.
# List the 20 most recent unread messages curl "https://api.mailpipe.dev/v1/messages?mailbox_id=mbx_01hx8&unread=true&limit=20" \ -H "Authorization: Bearer mp_live_your-api-key-here"
curl "https://api.mailpipe.dev/v1/messages/msg_01hx8yq3vdkj1p4nze7rbc56wf" \ -H "Authorization: Bearer mp_live_your-api-key-here"
const client = new MailpipeClient({ apiKey: process.env.MAILPIPE_API_KEY });
// List unread messages
const { data: messages } = await client.messages.list({
mailbox_id: 'mbx_01hx8yq3vdkj1p4nze7rbc56wf',
unread: true,
limit: 20,
});
// Fetch full message
const message = await client.messages.get('msg_01hx8yq3vdkj1p4nze7rbc56wf');
console.log(message.subject, message.textBody);
// Mark as read
await client.messages.update('msg_01hx8yq3vdkj1p4nze7rbc56wf', { is_read: true });Messages are stored for 90 days on the Free plan and 1 year on Pro and above. Attachments count toward your storage quota. See the Supabase integration guide to query message data directly from your own database client.
Mailpipe supports two patterns for consuming inbound messages. The right choice depends on your use case.
Periodically call GET /v1/messages to check for new messages. Simple to implement but introduces latency equal to your polling interval.
Mailpipe calls your endpoint the instant mail arrives. Real-time and efficient, but requires a publicly reachable HTTPS endpoint.
Recommendation
Use webhooks for production applications where low latency matters (e.g., customer support inboxes, automated reply systems). Use polling for scripts, batch processing, or development environments where you don't have a public endpoint. Many applications use both: webhooks for real-time processing and the polling API as a fallback to reconcile missed events.