Inbound Routing Webhooks
Webhook structure, payload schema, validation, retry mechanism, and sample implementation for Maileroo Inbound Routing.
We send webhooks in HTTP POST with a JSON payload.
This payload contains all essential information such as domain, envelope sender, message ID, and an array of headers.
Headers are represented as an array because multiple headers can have the same name but different values.
Our system will automatically attempt to extract the last message from the email thread (both for plaintext and HTML). This is available in body.stripped_plaintext and body.stripped_html.
To keep things simple, we do not include the attachments inside the payload, but you can loop through attachments and download them directly as we upload them to our S3 compatible storage. This is also true for Raw Mime.
Schema
type EmailEvent struct {
Id string `json:"_id"`
MessageId string `json:"message_id"`
Domain string `json:"domain"`
EnvelopeSender string `json:"envelope_sender"`
Recipients []string `json:"recipients"`
Headers map[string][]string `json:"headers"`
Body EmailBody `json:"body"`
Attachments *[]DownloadableAttachment `json:"attachments"`
SPFResult spf.Result `json:"spf_result"`
DKIMResult bool `json:"dkim_result"`
IsDMARCAligned bool `json:"is_dmarc_aligned"`
IsSpam bool `json:"is_spam"`
DeletionUrl string `json:"deletion_url"`
ValidationUrl string `json:"validation_url"`
ProcessedAt int64 `json:"processed_at"`
}
type DownloadableAttachment struct {
Filename string `json:"filename"`
ContentId string `json:"content_id"`
ContentType string `json:"content_type"`
URL string `json:"url"`
Size int `json:"size"`
}
type EmailBody struct {
Plaintext string `json:"plaintext"`
StrippedPlaintext string `json:"stripped_plaintext"`
HTML string `json:"html"`
StrippedHTML string `json:"stripped_html"`
OtherParts *[]OtherPart `json:"other_parts"`
RawMime RawMime `json:"raw_mime"`
}
type OtherPart struct {
ContentType string `json:"content_type"`
Contents string `json:"contents"`
}
type RawMime struct {
URL string `json:"url"`
Size int `json:"size"`
}Sample Payload
{
"_id": "677730adac1b7a32de362ccd",
"message_id": "CALx1tRL=zMbjat+bmzdJsLG_67OOvVGJbUxCZNc6F3EagoLfkw@mail.gmail.com",
"domain": "mail.maileroo.com",
"envelope_sender": "[email protected]",
"recipients": [
"[email protected]"
],
"headers": {
"Content-Type": [
"multipart/mixed; boundary=\"000000000000b48c26062a2147cb\""
],
"Date": [
"Thu, 26 Dec 2024 11:17:56 +1100"
],
"From": [
"Maileroo <[email protected]>"
],
"In-Reply-To": [
"<CAPmyPAQhzAi+82KqQ63C=JV+Ke9k_UAHw674Rrx0LWyjX+i2gg@mail.gmail.com>"
],
"Message-Id": [
"<CALx1tRL=zMbjat+bmzdJsLG_67OOvVGJbUxCZNc6F3EagoLfkw@mail.gmail.com>"
],
"Mime-Version": [
"1.0"
],
"References": [
"<CAPmyPAQhzAi+82KqQ63C=JV+Ke9k_UAHw674Rrx0LWyjX+i2gg@mail.gmail.com>"
],
"Subject": [
"Re: Test Email from Maileroo"
],
"To": [
"[email protected]"
]
},
"body": {
"plaintext": "PLAIN TEXT HERE",
"stripped_plaintext": "EXTRACTED CONTENT HERE",
"html": "HTML HERE",
"stripped_html": "EXTRACTED HTML HERE",
"other_parts": null,
"raw_mime": {
"url": "https://roo-inbound-store.nbg1.your-objectstorage.com/677730adac1b7a32de362ccd/raw.mime?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=PXCGE7SXA8IQG411X833%2F20250103%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250103T003453Z&X-Amz-Expires=259200&X-Amz-SignedHeaders=host&x-id=GetObject&X-Amz-Signature=be4af21d67d9f9be0b51eabecb054f1b7bf3d6b8efef0f74c5aef7a9a9e83322",
"size": 3248
}
},
"attachments": [
{
"filename": "favicon-16x16.webp",
"content_id": "<f_m54krol90>",
"content_type": "image/webp",
"url": "https://roo-inbound-store.nbg1.your-objectstorage.com/677730adac1b7a32de362ccd/favicon-16x16.webp?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=PXCGE7SXA8IQG411X833%2F20250103%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250103T003453Z&X-Amz-Expires=259200&X-Amz-SignedHeaders=host&x-id=GetObject&X-Amz-Signature=376b1f15b27d0990493c9c67e3856e6d16b91ada1f75ae3c2c05b31511425c2d",
"size": 376
}
],
"spf_result": "pass",
"dkim_result": true,
"is_dmarc_aligned": true,
"is_spam": false,
"deletion_url": "https://inbound-api.maileroo.net/email/677730adac1b7a32de362ccd/6df64578a500659d0ecfe163e91b0ab0",
"validation_url": "https://inbound-api.maileroo.net/validate-callback/677730adac1b7a32de362cce/b71bfca7653deeb8802c87124ba65212",
"processed_at": 1735864493
}Validation (Authenticity)
Our webhook system uses a simple yet effective method for verifying the authenticity of incoming requests. Unlike traditional systems that rely on shared secrets or signatures, we provide a straightforward approach.
For every webhook, you are provided with a unique validation_url. To confirm that a webhook request is legitimate, you can make a GET or POST request to this URL. Based on your request, the server will respond with one of the following JSON responses:
For a failed webhook:
{"success":false}And, for a successful webhook:
{"success":true}However, once you hit this URL, all subsequent attempts will return as failure. You are only supposed to call this validation_url once.
Delete Emails on Demand
All emails are stored for 72 hours. However, if you wish to delete an email from our storage after you download the raw message and attachments, please send a DELETE request to the deletion_url present in the email. Calling this endpoint will result in all data associated to this message being purged from our databases.
DELETE https://inbound-api.maileroo.net/email/677730adac1b7a32de362ccd/6df64578a500659d0ecfe163e91b0ab0{
"success": true
}Retry Mechanism
The first callback is sent almost immediately. The webhook retry mechanism will attempt to deliver the webhook payload at the following intervals if a successful HTTP 200 OK response is not received:
- 5 minutes
- 10 minutes
- 15 minutes
- 30 minutes
- 1 hour
- 2 hours
- 4 hours
- 6 hours
The system will continue through these intervals sequentially until a 200 OK response is received, signaling successful delivery, or the retries are exhausted.
This approach ensures the payload is reattempted with increasing intervals, giving the receiving system sufficient time to recover from potential issues.
Sample Implementation
If you were to handle a webhook in PHP, this is how you would do it.
function makeHttpRequest($url, $method = "GET") {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
if ($method === "DELETE") {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'response' => $response,
'httpCode' => $httpCode
];
}
// Validate the webhook
function validateWebhook($validationUrl) {
$result = makeHttpRequest($validationUrl);
if ($result['httpCode'] === 200) {
$data = json_decode($result['response'], true);
if (isset($data['success']) && $data['success'] === true) {
echo "Webhook validation successful.\n";
return true;
}
}
echo "Webhook validation failed.\n";
return false;
}
// Delete email
function deleteEmail($deletionUrl) {
$result = makeHttpRequest($deletionUrl, "DELETE");
if ($result['httpCode'] === 200) {
echo "Email deleted successfully.\n";
} else {
echo "Failed to delete email.\n";
}
}
// Example usage
$payload = json_decode(file_get_contents('php://input'), true);
if (!isset($payload['validation_url'], $payload['deletion_url'])) {
echo "Invalid payload.\n";
exit;
}
$validationUrl = $payload['validation_url'];
$deletionUrl = $payload['deletion_url'];
// Validate the webhook
if (validateWebhook($validationUrl)) {
// Perform any further processing if validation is successful
echo "Processing email...\n";
// Delete the email
deleteEmail($deletionUrl);
}