API reference
Webhook payload documentation
ReceiveHQ delivers inbound email to your HTTPS endpoint as an HTTP POST. Choose a payload format per endpoint so your existing Postmark, Mailgun, SendGrid, CloudMailin, Inbound, EmailConnect, or MailSlurp handler can stay unchanged.
Overview
Messages arrive over SMTP, are parsed into a canonical Postmark-shaped object, then encoded for the format configured on the webhook endpoint. Format is snapshotted when a delivery is created — changing an endpoint later does not rewrite in-flight deliveries.
POSTHTTPS webhook URL6 formatsOptional Basic auth
- postmark — application/json (default)
- mailgun_sendgrid — multipart/form-data with both providers’ field names
- cloudmailin — application/json
- inbound — application/json (inbound.new shape)
- emailconnect — application/json
- mailslurp — application/json (+ ReceiveHQ body extension)
Delivery protocol
Request
| Item | Value |
|---|
| Method | POST |
| URL | Your endpoint webhook URL |
| User-Agent | receivehq-inbound/1.0 |
| Content-Type | application/json for Postmark, CloudMailin, Inbound, EmailConnect, and MailSlurp; multipart boundary set automatically for Mailgun / SendGrid |
| Authorization | Optional Basic <base64(user:pass)> when configured on the endpoint |
Response semantics
| Status | Behavior |
|---|
| 2xx | Success — delivery acknowledged |
| 406 | Permanent drop — no further retries |
| Other | Transient failure — retried with backoff |
ReceiveHQ does not send provider HMAC / signature headers (Postmark X-Postmark-Signature, Mailgun signing, etc.). Authenticate with HTTPS + Basic auth (or your own shared-secret query/path scheme).
Postmark (JSON)
format = postmarkContent-Type: application/json
Compatible with Postmark Inbound webhook bodies. The full parsed message — including RawEmail — is posted as one JSON object.
Top-level fields
| Field | Type | Description |
|---|
| FromName | string | Display name from the From header |
| MessageStream | string | Always "inbound" |
| From | string | Sender email address only (not Name <email>) |
| FromFull | Address | Structured From: Email, Name, MailboxHash |
| To | string | Formatted To recipients |
| ToFull | Address[] | Structured To list |
| Cc / CcFull | string / Address[] | Carbon copy |
| Bcc / BccFull | string / Address[] | Blind carbon copy when present in the MIME |
| OriginalRecipient | string | SMTP envelope RCPT TO (or first To if envelope missing) |
| Subject | string | Subject line |
| MessageID | string | Message-ID without surrounding angle brackets, or a generated UUID |
| ReplyTo | string | Reply-To header value |
| MailboxHash | string | Plus-address local-part after + (e.g. invoices+abc@… → abc) |
| Date | string | Parsed Date / Date header |
| TextBody | string | Plain-text body |
| HtmlBody | string | HTML body |
| StrippedTextReply | string | Best-effort quoted-reply strip (not identical to Postmark) |
| Tag | string | Always "" |
| Headers | Header[] | All headers as { Name, Value } |
| Attachments | Attachment[] | See Attachments |
| RawEmail | string | Full raw MIME message (UTF-8 string) |
Address object
| Field | Type | Description |
|---|
| Email | string | Mailbox address |
| Name | string | Display name (may be empty) |
| MailboxHash | string | Plus-tag for this address when present |
Attachment object
| Field | Type | Description |
|---|
| Name | string | Filename |
| Content | string | Base64-encoded bytes |
| ContentType | string | MIME type |
| ContentLength | number | Decoded byte length |
| ContentID | string | null | CID for inline parts; null otherwise |
Example
{
"FromName": "Acme Billing",
"MessageStream": "inbound",
"From": "vendor@acme.com",
"FromFull": {
"Email": "vendor@acme.com",
"Name": "Acme Billing",
"MailboxHash": ""
},
"To": "invoices@yourdomain.com",
"ToFull": [
{
"Email": "invoices@yourdomain.com",
"Name": "",
"MailboxHash": ""
}
],
"Cc": "",
"CcFull": [],
"Bcc": "",
"BccFull": [],
"OriginalRecipient": "invoices@yourdomain.com",
"Subject": "Invoice 1042",
"MessageID": "msg-id@mx.example",
"ReplyTo": "",
"MailboxHash": "",
"Date": "Tue, 8 Sep 2026 12:00:00 +0000",
"TextBody": "Please find invoice 1042 attached.",
"HtmlBody": "<p>Please find invoice 1042 attached.</p>",
"StrippedTextReply": "",
"Tag": "",
"Headers": [
{ "Name": "Subject", "Value": "Invoice 1042" },
{ "Name": "From", "Value": "Acme Billing <vendor@acme.com>" },
{ "Name": "Date", "Value": "Tue, 8 Sep 2026 12:00:00 +0000" }
],
"Attachments": [
{
"Name": "invoice.pdf",
"Content": "<base64>",
"ContentType": "application/pdf",
"ContentLength": 24830,
"ContentID": null
}
],
"RawEmail": "From: vendor@acme.com\r\nTo: invoices@yourdomain.com\r\n..."
}
Mailgun / SendGrid (multipart)
format = mailgun_sendgridContent-Type: multipart/form-data
A single multipart body includes both SendGrid Inbound Parse and Mailgun Routes field names. Handlers that expect either provider can read their own keys from the same request.
Each attachment is appended twice: SendGrid-style attachment1, attachment2, … and Mailgun-style attachment-1, attachment-2, … as binary file parts (not base64).
SendGrid-style fields
| Field | Type | Description |
|---|
| from | string | Sender (Postmark From) |
| to | string | Formatted To |
| cc | string | Formatted Cc |
| subject | string | Subject |
| text | string | Plain-text body |
| html | string | HTML body |
| envelope | JSON string | { to: string[], from: string } — SMTP envelope recipients + mail from |
| headers | string | Raw header block: Name: Value lines joined by \n |
| charsets | JSON string | Fixed UTF-8 map for to, from, subject, text, html |
| email | string | Full raw MIME |
| attachments | string | Attachment count as a decimal string |
| attachmentN | file | Binary attachment parts: attachment1, attachment2, … |
| attachment-info | JSON string | Map of SendGrid keys → { filename, type, content-id? } |
Mailgun-style fields
| Field | Type | Description |
|---|
| sender | string | Envelope / From address |
| recipient | string | Primary recipient (OriginalRecipient) |
| body-plain | string | Plain-text body |
| body-html | string | HTML body |
| message-headers | JSON string | Array of [Name, Value] pairs |
| body-mime | string | Full raw MIME |
| attachment-count | string | Attachment count as a decimal string |
| attachment-N | file | Binary parts: attachment-1, attachment-2, … |
| content-id-map | JSON string | Map of Content-ID → Mailgun attachment key (e.g. attachment-1) |
Field sketch
from=vendor@acme.com
to=invoices@yourdomain.com
cc=
subject=Invoice 1042
text=Please find invoice 1042 attached.
html=<p>Please find invoice 1042 attached.</p>
envelope={"to":["invoices@yourdomain.com"],"from":"vendor@acme.com"}
headers=Subject: Invoice 1042\nFrom: Acme Billing <vendor@acme.com>
charsets={"to":"UTF-8","from":"UTF-8","subject":"UTF-8","text":"UTF-8","html":"UTF-8"}
email=<raw MIME>
sender=vendor@acme.com
recipient=invoices@yourdomain.com
body-plain=Please find invoice 1042 attached.
body-html=<p>Please find invoice 1042 attached.</p>
message-headers=[["Subject","Invoice 1042"],["From","Acme Billing <vendor@acme.com>"]]
body-mime=<raw MIME>
attachments=1
attachment-count=1
attachment1=<binary file>
attachment-1=<binary file>
attachment-info={"attachment1":{"filename":"invoice.pdf","type":"application/pdf"}}
content-id-map={}
CloudMailin (JSON)
format = cloudmailinContent-Type: application/json
Normalised hash shape used by CloudMailin JSON webhooks. There is no top-level raw MIME field in this format (use Postmark or Mailgun / SendGrid if you need the full .eml).
Top-level fields
| Field | Type | Description |
|---|
| headers | object | Header map. Duplicate names become string[]; otherwise string |
| envelope | object | SMTP envelope metadata (see below) |
| plain | string | Plain-text body |
| html | string | HTML body |
| reply_plain | string | Stripped reply text (from Postmark StrippedTextReply) |
| attachments | array | Base64 attachments (see below) |
envelope
| Field | Type | Description |
|---|
| to | string | Primary recipient |
| from | string | Envelope mail from / From |
| recipients | string[] | All envelope RCPT TO addresses |
| helo_domain | string | Always "" (not available after SMTP accept) |
| remote_ip | string | Always "" |
| tls | boolean | Always true |
Attachment object
| Field | Type | Description |
|---|
| content | string | Base64-encoded bytes |
| file_name | string | Filename |
| content_type | string | MIME type |
| size | number | Byte length |
| disposition | string | "inline" if Content-ID present, else "attachment" |
| content_id | string? | Only included when the part has a Content-ID |
Example
{
"headers": {
"Subject": "Invoice 1042",
"From": "Acme Billing <vendor@acme.com>",
"Date": "Tue, 8 Sep 2026 12:00:00 +0000"
},
"envelope": {
"to": "invoices@yourdomain.com",
"from": "vendor@acme.com",
"helo_domain": "",
"remote_ip": "",
"recipients": ["invoices@yourdomain.com"],
"tls": true
},
"plain": "Please find invoice 1042 attached.",
"html": "<p>Please find invoice 1042 attached.</p>",
"reply_plain": "",
"attachments": [
{
"content": "<base64>",
"file_name": "invoice.pdf",
"content_type": "application/pdf",
"size": 24830,
"disposition": "attachment"
}
]
}
Inbound / inbound.new (JSON)
format = inboundContent-Type: application/json
Compatible with inbound.new webhook bodies (event email.received). Useful when migrating from Inbound without rewriting your handler.
Attachments include a ReceiveHQ content field with base64 bytes. downloadUrl is left empty — ReceiveHQ does not host vendor-style attachment download URLs.
Top-level fields
| Field | Type | Description |
|---|
| event | string | Always "email.received" |
| timestamp | string | ISO 8601 send time |
| email | object | Parsed email object (see below) |
| endpoint | object | Endpoint metadata: id, name, type "webhook" |
email
| Field | Type | Description |
|---|
| id | string | ReceiveHQ message id (or Message-ID fallback) |
| messageId | string | SMTP Message-ID header value |
| from / to | object | Address group with text + addresses[] of { name, address } |
| recipient | string | Primary envelope / OriginalRecipient address |
| subject | string | Subject line |
| receivedAt | string | ISO 8601 received time |
| parsedData | object | Full parsed payload including bodies, raw MIME, attachments, headers |
| cleanedContent | object | Cleaned html/text mirrors plus flags hasHtml / hasText |
email.parsedData
| Field | Type | Description |
|---|
| textBody | string | Plain-text body |
| htmlBody | string | HTML body |
| raw | string | Full raw MIME (UTF-8 string) |
| attachments | array | Attachment list (see below) |
| headers | object | Flat header map (duplicate names joined with comma) |
Attachment object
| Field | Type | Description |
|---|
| filename | string | Filename |
| contentType | string | MIME type |
| size | number | Byte length |
| downloadUrl | string | Always "" (use content instead) |
| content | string | ReceiveHQ extension: base64-encoded bytes |
Example
{
"event": "email.received",
"timestamp": "2026-09-08T12:00:00.000Z",
"email": {
"id": "msg-db-id",
"messageId": "<msg-id@mx.example>",
"from": {
"text": "Acme Billing <vendor@acme.com>",
"addresses": [{ "name": "Acme Billing", "address": "vendor@acme.com" }]
},
"to": {
"text": "invoices@yourdomain.com",
"addresses": [{ "name": null, "address": "invoices@yourdomain.com" }]
},
"recipient": "invoices@yourdomain.com",
"subject": "Invoice 1042",
"receivedAt": "2026-09-08T12:00:00.000Z",
"parsedData": {
"messageId": "<msg-id@mx.example>",
"date": "2026-09-08T12:00:00.000Z",
"subject": "Invoice 1042",
"from": { "text": "Acme Billing <vendor@acme.com>", "addresses": [{ "name": "Acme Billing", "address": "vendor@acme.com" }] },
"to": { "text": "invoices@yourdomain.com", "addresses": [{ "name": null, "address": "invoices@yourdomain.com" }] },
"cc": null,
"bcc": null,
"replyTo": null,
"textBody": "Please find invoice 1042 attached.",
"htmlBody": "<p>Please find invoice 1042 attached.</p>",
"raw": "From: vendor@acme.com\r\n...",
"attachments": [
{
"filename": "invoice.pdf",
"contentType": "application/pdf",
"size": 24830,
"contentDisposition": "attachment",
"downloadUrl": "",
"content": "<base64>"
}
],
"headers": { "Subject": "Invoice 1042" }
},
"cleanedContent": {
"html": "<p>Please find invoice 1042 attached.</p>",
"text": "Please find invoice 1042 attached.",
"hasHtml": true,
"hasText": true,
"attachments": [],
"headers": {}
}
},
"endpoint": { "id": "ep_id", "name": "Support", "type": "webhook" }
}
EmailConnect (JSON)
format = emailconnectContent-Type: application/json
Compatible with EmailConnect.eu default webhook JSON (message + envelope). Enrichment blocks such as spam scores are omitted unless ReceiveHQ has equivalent data.
Attachments include inline base64 content (sync upload style). Standard Webhooks signing headers are not sent — use HTTPS + optional Basic auth.
Top-level fields
| Field | Type | Description |
|---|
| message | object | Message body, addresses, and attachments |
| envelope | object | Envelope / processing metadata |
message
| Field | Type | Description |
|---|
| sender | object | sender.email and sender.name |
| recipient | object | recipient.email, name (null), tag (plus-address local part when present) |
| subject | string | Subject line |
| date | string | ISO 8601 date |
| content | object | content.text / html / markdown / links / optional reply |
| attachments | array | Attachment list with content base64, status completed, uploadType sync |
message.content
| Field | Type | Description |
|---|
| text | string | Plain-text body |
| html | string | HTML body |
| markdown | string | Plain text copied as markdown (no converter) |
| links | array | Always [] (link extraction not implemented) |
| reply | object? | Present when stripped reply text is available: { text } |
envelope
| Field | Type | Description |
|---|
| messageId | string | SMTP Message-ID |
| returnPath | string | Envelope mail from |
| allRecipients | object | to / cc / bcc address arrays |
| headers | object | Flat header map |
| processed | object | timestamp, domain, alias, originalSize |
Example
{
"message": {
"sender": { "email": "vendor@acme.com", "name": "Acme Billing" },
"recipient": { "email": "invoices@yourdomain.com", "name": null, "tag": null },
"subject": "Invoice 1042",
"date": "2026-09-08T12:00:00.000Z",
"content": {
"text": "Please find invoice 1042 attached.",
"html": "<p>Please find invoice 1042 attached.</p>",
"markdown": "Please find invoice 1042 attached.",
"links": []
},
"attachments": [
{
"filename": "invoice.pdf",
"contentType": "application/pdf",
"size": 24830,
"content": "<base64>",
"status": "completed",
"uploadType": "sync"
}
]
},
"envelope": {
"messageId": "<msg-id@mx.example>",
"returnPath": "vendor@acme.com",
"xOriginalTo": "invoices@yourdomain.com",
"xMailer": null,
"allRecipients": {
"to": ["invoices@yourdomain.com"],
"cc": [],
"bcc": []
},
"headers": { "Subject": "Invoice 1042" },
"processed": {
"timestamp": "2026-09-08T12:00:00.000Z",
"domain": "yourdomain.com",
"alias": "invoices@yourdomain.com",
"originalSize": 1234
}
}
}
MailSlurp (JSON)
format = mailslurpContent-Type: application/json
Shaped like MailSlurp NEW_EMAIL webhook payloads (metadata fields). Real MailSlurp webhooks omit bodies — ReceiveHQ adds textBody, htmlBody, and attachments so the webhook is usable without a MailSlurp API.
textBody, htmlBody, and attachments are ReceiveHQ extensions. Vendor signature headers (x-signature) are not sent.
Top-level fields
| Field | Type | Description |
|---|
| messageId | string | Idempotent delivery id |
| webhookId | string | Endpoint id |
| eventName | string | Always "NEW_EMAIL" |
| emailId | string | ReceiveHQ message id |
| createdAt | string | ISO 8601 event time |
| to / from / cc / bcc | string[] / string | to (string[]), from (string), cc / bcc (string[]) |
| subject | string | Subject line |
| attachmentMetaDatas | array | Vendor-style attachment metadata (id, name, contentType, contentLength) |
| textBody | string | ReceiveHQ extension: plain-text body |
| htmlBody | string | ReceiveHQ extension: HTML body |
| attachments | array | ReceiveHQ extension: metadata + base64 content |
Example
{
"messageId": "delivery-id",
"webhookId": "endpoint-id",
"eventName": "NEW_EMAIL",
"webhookName": "QA",
"inboxId": "endpoint-id",
"domainId": null,
"emailId": "message-id",
"createdAt": "2026-09-08T12:00:00.000Z",
"to": ["invoices@yourdomain.com"],
"from": "vendor@acme.com",
"cc": [],
"bcc": [],
"subject": "Invoice 1042",
"attachmentMetaDatas": [
{
"id": "message-id-att-0",
"name": "invoice.pdf",
"contentType": "application/pdf",
"contentLength": 24830
}
],
"textBody": "Please find invoice 1042 attached.",
"htmlBody": "<p>Please find invoice 1042 attached.</p>",
"attachments": [
{
"id": "message-id-att-0",
"name": "invoice.pdf",
"contentType": "application/pdf",
"contentLength": 24830,
"content": "<base64>"
}
]
}
Attachments
- Postmark / CloudMailin — base64 in JSON (Content / content).
- Mailgun / SendGrid — raw binary multipart file parts; metadata in attachment-info / content-id-map.
- Inbound / EmailConnect — base64 in JSON (content). Inbound downloadUrl is empty.
- MailSlurp — attachmentMetaDatas for metadata; attachments[] with content for bytes (ReceiveHQ extension).
- Inline images with a Content-ID are marked accordingly in each format.
- Prefer reading structured bodies (TextBody / plain / text) for app logic; use raw MIME when you need full fidelity.
Compatibility notes
- Canonical parse is Postmark-shaped; other formats are transforms of that object.
- From in Postmark JSON is the bare email address. Use FromFull.Name for the display name.
- StrippedTextReply / reply_plain are best-effort and may differ from Postmark’s stripper.
- CloudMailin helo_domain and remote_ip are empty stubs; tls is hardcoded true.
- Inbound attachment content and MailSlurp textBody/htmlBody/attachments are ReceiveHQ extensions documented above — not present on the vendor wire formats.
- Unknown format IDs normalize to postmark. Cloudflare Workers bindings and ParseForce per-customer schemas are not offered as formats.
Configure the same format your handler was written for. Switching from Postmark JSON to multipart (or the reverse) without updating your parser will look like “empty” fields even though delivery succeeded with 2xx.