Documentation Index Fetch the complete documentation index at: https://docs.mailbreeze.com/llms.txt
Use this file to discover all available pages before exploring further.
When you configure a webhook destination for inbound email, MailBreeze sends a POST request to your endpoint with the full email data. This allows you to parse emails, extract information, trigger workflows, and integrate with your application.
Webhook Payload
MailBreeze sends a JSON payload with the complete email data:
{
"id" : "inb_a1b2c3d4e5f6" ,
"event" : "inbound.received" ,
"timestamp" : "2026-01-27T15:30:05.123Z" ,
"data" : {
"from" : {
"address" : "customer@example.com" ,
"name" : "Jane Customer"
},
"to" : [
{
"address" : "support@yourdomain.com" ,
"name" : null
}
],
"cc" : [
{
"address" : "manager@example.com" ,
"name" : "Manager"
}
],
"replyTo" : {
"address" : "customer@example.com" ,
"name" : "Jane Customer"
},
"subject" : "Question about my order #12345" ,
"text" : "Hi, \n\n I have a question about my recent order... \n\n Thanks, \n Jane" ,
"html" : "<p>Hi,</p><p>I have a question about my recent order...</p><p>Thanks,<br>Jane</p>" ,
"attachments" : [
{
"id" : "att_xyz789" ,
"filename" : "receipt.pdf" ,
"contentType" : "application/pdf" ,
"size" : 45678 ,
"url" : "https://api.mailbreeze.com/v1/inbound/inb_a1b2c3d4e5f6/attachments/att_xyz789"
}
],
"headers" : {
"message-id" : "<CADfH8w3abc123@mail.example.com>" ,
"date" : "Mon, 27 Jan 2026 10:30:00 -0500" ,
"in-reply-to" : "<previous-message-id@yourdomain.com>" ,
"references" : "<original-message-id@yourdomain.com>"
},
"authentication" : {
"spf" : "pass" ,
"dkim" : "pass" ,
"dmarc" : "pass"
},
"spamScore" : 0.1 ,
"receivedAt" : "2026-01-27T15:30:05.123Z"
}
}
Payload Fields
Unique identifier for this inbound email.
Event type. Always inbound.received for new emails.
ISO 8601 timestamp when the webhook was sent.
Sender information with address and name fields.
Array of recipient objects matching your domain.
Array of CC recipient objects.
Plain text body of the email.
HTML body of the email (if present).
Array of attachment objects with metadata and download URLs.
Key email headers including Message-ID, Date, In-Reply-To, and References.
SPF, DKIM, and DMARC check results (pass, fail, none).
Spam score from 0.0 (not spam) to 1.0 (definitely spam).
Webhook Requirements
Your endpoint must meet these requirements:
Requirement Details Protocol HTTPS only (HTTP endpoints will be rejected) Method Accept POST requests Response Return 200 OK within 30 seconds Content-Type Accept application/json Availability Must be publicly accessible
If your endpoint returns a non-2xx status code or times out, MailBreeze will retry the webhook. See Retry Behavior below.
Verifying Webhooks
To ensure webhooks are genuinely from MailBreeze, verify the signature header.
Every webhook includes an X-MailBreeze-Signature header:
X-MailBreeze-Signature: t=1706369405,v1=abc123def456...
The header contains:
t — Unix timestamp when the signature was generated
v1 — HMAC-SHA256 signature of the payload
Verification Steps
Extract the timestamp and signature
Parse the header to get t (timestamp) and v1 (signature). const sigHeader = req . headers [ 'x-mailbreeze-signature' ];
const [ tPart , v1Part ] = sigHeader . split ( ',' );
const timestamp = tPart . split ( '=' )[ 1 ];
const signature = v1Part . split ( '=' )[ 1 ];
Prepare the signed payload
Concatenate the timestamp and raw request body with a period. const signedPayload = ` ${ timestamp } . ${ rawBody } ` ;
Compute expected signature
Calculate HMAC-SHA256 using your webhook secret. const crypto = require ( 'crypto' );
const expectedSignature = crypto
. createHmac ( 'sha256' , webhookSecret )
. update ( signedPayload )
. digest ( 'hex' );
Compare signatures
Use timing-safe comparison to prevent timing attacks. const isValid = crypto . timingSafeEqual (
Buffer . from ( signature ),
Buffer . from ( expectedSignature )
);
Validate timestamp
Reject requests older than 5 minutes to prevent replay attacks. const tolerance = 5 * 60 * 1000 ; // 5 minutes
const isRecent = Date . now () - ( timestamp * 1000 ) < tolerance ;
Complete Verification Example
Node.js (Express)
Python (Flask)
Go
PHP
const crypto = require ( 'crypto' );
const express = require ( 'express' );
const app = express ();
// Must use raw body for signature verification
app . use ( '/webhook' , express . raw ({ type: 'application/json' }));
app . post ( '/webhook/inbound' , ( req , res ) => {
const signature = req . headers [ 'x-mailbreeze-signature' ];
const webhookSecret = process . env . MAILBREEZE_WEBHOOK_SECRET ;
if ( ! verifySignature ( signature , req . body , webhookSecret )) {
return res . status ( 401 ). send ( 'Invalid signature' );
}
const payload = JSON . parse ( req . body );
// Process the email
console . log ( 'Received email from:' , payload . data . from . address );
res . status ( 200 ). send ( 'OK' );
});
function verifySignature ( sigHeader , body , secret ) {
const [ tPart , v1Part ] = sigHeader . split ( ',' );
const timestamp = tPart . split ( '=' )[ 1 ];
const signature = v1Part . split ( '=' )[ 1 ];
// Check timestamp (5 minute tolerance)
const age = Date . now () - ( parseInt ( timestamp ) * 1000 );
if ( age > 5 * 60 * 1000 ) return false ;
// Compute expected signature
const signedPayload = ` ${ timestamp } . ${ body } ` ;
const expected = crypto
. createHmac ( 'sha256' , secret )
. update ( signedPayload )
. digest ( 'hex' );
// Timing-safe comparison
return crypto . timingSafeEqual (
Buffer . from ( signature ),
Buffer . from ( expected )
);
}
Retry Behavior
If your endpoint fails to respond with 200 OK, MailBreeze retries with exponential backoff:
Attempt Delay After Failure 1 Immediate 2 1 minute 3 5 minutes 4 30 minutes 5 2 hours 6 8 hours 7 24 hours
After 7 failed attempts, the webhook is marked as failed. Failed webhooks are visible in Domains > Inbound > Delivery Logs .
Your endpoint should handle duplicate deliveries gracefully. Use the id field to deduplicate emails in case a webhook is retried despite successful processing.
Downloading Attachments
Attachment URLs require authentication. Include your API key when downloading:
curl -o receipt.pdf \
-H "x-api-key: sk_live_xxx" \
"https://api.mailbreeze.com/v1/inbound/inb_a1b2c3d4e5f6/attachments/att_xyz789"
Attachment URLs expire after 7 days. Download attachments promptly if you need to retain them.
Testing Webhooks
Local Development
Use a tunneling service to expose your local server:
ngrok http 3000
# Use the generated URL (e.g., https://abc123.ngrok.io/webhook/inbound)
Test Endpoint
Send a test webhook from the MailBreeze dashboard:
Go to Domains > your domain > Inbound Settings > Routes
Select your webhook route
Click Send Test Webhook
This sends a sample payload to verify your endpoint is working.
Manual Testing
Send an email to an address on your domain and monitor your endpoint logs.
Error Handling
Return appropriate HTTP status codes:
Code Meaning MailBreeze Behavior 200Success Mark as delivered 4xxClient error (bad request, unauthorized) Retry (may be transient) 5xxServer error Retry with backoff Timeout No response in 30s Retry with backoff
If you need more time to process an email, return 200 OK immediately and process asynchronously. Use a message queue (Redis, SQS, etc.) for heavy processing.
Common Patterns
Parse Reply-To Thread
Extract the thread ID from reply-to addresses:
// Your app sends from: reply+order_123@inbound.yourapp.com
// Customer replies to that address
app . post ( '/webhook/inbound' , ( req , res ) => {
const { to } = req . body . data ;
const replyAddress = to [ 0 ]. address ;
// Extract order ID from address
const match = replyAddress . match ( /reply \+ ( . + ) @/ );
if ( match ) {
const orderId = match [ 1 ]; // "order_123"
// Associate this reply with the order
}
res . status ( 200 ). send ( 'OK' );
});
Filter Spam
Use the spam score and authentication results:
app . post ( '/webhook/inbound' , ( req , res ) => {
const { spamScore , authentication } = req . body . data ;
// Reject likely spam
if ( spamScore > 0.7 ) {
console . log ( 'Rejected spam email' );
return res . status ( 200 ). send ( 'OK' ); // Still return 200 to prevent retries
}
// Flag suspicious emails
if ( authentication . dmarc !== 'pass' ) {
console . log ( 'Warning: DMARC check failed' );
}
// Process legitimate email
// ...
res . status ( 200 ). send ( 'OK' );
});
Use headers for conversation threading:
app . post ( '/webhook/inbound' , ( req , res ) => {
const { headers } = req . body . data ;
const inReplyTo = headers [ 'in-reply-to' ];
const references = headers [ 'references' ];
if ( inReplyTo ) {
// This is a reply to a previous message
const originalMessageId = inReplyTo . replace ( / [ <> ] / g , '' );
// Look up the original conversation
}
res . status ( 200 ). send ( 'OK' );
});
Next Steps
Configure Routes Set up address-specific routing rules
Inbound API Retrieve inbound emails via API