Migrate from an email parser tool to MailWebhook
Use this guide when a current email parser tool extracts fields from forwarded messages and sends structured data to an app, webhook, spreadsheet, CRM, or operations workflow.
MailWebhook moves the workflow closer to an email-to-webhook pipeline. You connect the mailbox source, define route rules, choose Generic JSON or Custom JSON, and send a signed JSON webhook to your receiver.
For structured payload positioning, see Email to JSON. For the broader email-to-webhook workflow, see Email to Webhook.
Table of contents
- Short answer
- What changes during migration
- Prerequisites
- 1. Inventory the parser workflow
- 2. Choose the mailbox source
- 3. Create the receiver endpoint
- 4. Recreate template selection as route rules
- 5. Start with Generic JSON
- 6. Map extracted fields with Custom JSON
- Full route JSON example
- Attachment migration
- Endpoint behavior
- Cutover plan
- Validation checklist
- Common migration issues
- Related docs
Short answer
Do not migrate an email parser tool by copying every old template rule into one receiver.
Start with the workflow contract:
- Choose the mailbox source that receives the emails.
- Recreate message selection with route rules.
- Start with
map.generic_jsonso you can inspect normalized messages. - Move stable field mapping into
map.custom_json. - Use extraction helpers only where the email format is stable enough.
- Keep business validation and fallback handling in your receiver.
- Verify
X-MailWebhook-Signature. - Store
X-Idempotency-Keybefore creating downstream records.
MailWebhook is a good fit when the end state should be a signed JSON webhook event with clear routing, mapping, delivery history, retries, replay, and attachment metadata.
What changes during migration
Most parser tools center the workflow around extraction templates. MailWebhook centers it around mailbox intake, route matching, and webhook payload output.
| Old parser-tool area | MailWebhook migration target |
|---|---|
| Parser inbox or forwarding address | Gmail, Microsoft 365, Office 365, Outlook, IMAP, or hosted mailbox source |
| Template selection | Route rule JSON |
| Extracted field list | Custom JSON output |
| Parser-specific field names | MailWebhook mapper variables and output keys |
| Parser webhooks or exports | MailWebhook endpoint delivery |
| File fields or attachment exports | Attachment metadata plus attachment download API |
| Parser retry or task logs | Delivery Attempts History and replay |
| Duplicate task handling | X-Idempotency-Key plus receiver-side storage |
Some old templates should stay in application code for a while. Move stable routing and payload shaping into MailWebhook first, then remove old parser logic after the receiver behaves the same way on real emails.
Prerequisites
You need:
- A MailWebhook project.
- A supported mailbox source for the incoming messages.
- A public HTTP or HTTPS receiver endpoint.
- A list of the old parser templates and extracted fields.
- Sample source emails for each important template.
- A receiver that accepts JSON
POSTrequests. - A plan for duplicate handling and replay.
MailWebhook endpoint URLs must resolve to public addresses. Private, loopback, link-local, reserved, and multicast targets are blocked.
1. Inventory the parser workflow
List what the current parser tool does before creating MailWebhook routes.
Capture:
- How emails reach the parser tool today.
- Template names or workflow names.
- Sender, recipient, subject, and attachment filters.
- Extracted fields and their downstream names.
- Whether a field comes from body text, HTML, a table, an attachment name, or a link.
- Required fields versus optional fields.
- Current webhook, export, or automation destination.
- Duplicate handling.
- Failure behavior when a field is missing.
Group templates by workflow, not by visual similarity. For example, invoice intake, lead intake, support intake, and alert routing usually deserve separate routes or endpoints.
2. Choose the mailbox source
Pick the source that should receive the email before routing starts.
| Source | Use when |
|---|---|
| Gmail or Google Workspace | The workflow already lives in a Gmail or Google Workspace inbox. |
| Microsoft 365, Office 365, or Outlook | The workflow belongs to an Outlook or Microsoft mailbox. |
| IMAP | The provider exposes a standard IMAP folder. |
| Hosted mailbox | A dedicated MailWebhook-hosted intake address is simpler than connecting an existing mailbox. |
| Loopback | You need a quick test route before using production mail. |
If the old parser tool used a generated parser address, update forwarding rules, application settings, groups, or aliases so matching emails can reach the MailWebhook source.
3. Create the receiver endpoint
Create a MailWebhook endpoint for your receiving service.
Example:
| Field | Value |
|---|---|
| Webhook URL | https://app.example.com/mailwebhook/parser-migration |
| Custom header | Authorization: Bearer intake-token |
| Timeout | Keep the default unless your receiver needs a different project standard. |
The receiver should:
- Read the raw request body before parsing JSON if it verifies
X-MailWebhook-Signature. - Store
X-Idempotency-Keybefore creating downstream records. - Return
2xxonly after it stores or enqueues the work. - Return a retryable status only when another delivery attempt can help.
- Log missing fields during migration instead of silently creating bad records.
4. Recreate template selection as route rules
Use route rules to select which emails enter a workflow.
For a dedicated parser inbox:
{
"to_emails": ["parser-intake@example.com"]
}
For a vendor-specific invoice workflow:
{
"to_emails": ["ap@example.com"],
"from_domains": ["vendor.example"],
"subject_contains": ["invoice", "receipt"],
"attachments_mime": ["application/pdf"],
"none": [
{
"subject_contains": ["test", "void"]
}
]
}
Top-level populated fields are AND-ed. In the second example, the message must match the recipient, sender domain, subject terms, and attachment MIME type, while avoiding subjects that mark a message as test or void.
Keep route rules narrow enough to avoid sending unrelated messages to a mapper that expects a specific format.
5. Start with Generic JSON
Use map.generic_json during the first pass.
{
"steps": [
{
"name": "map.generic_json",
"args": {}
}
]
}
Generic JSON helps you compare real emails against the old parser output.
Check:
schema.nameismailwebhook.generic.message.subjectmatches the source email.message.fromandmessage.tocontain the expected addresses.- The
bodyobject contains the text or HTML content your old parser used. - Attachment descriptors appear under the
attachmentskey inside thebodyobject. meta.sourcematches the mailbox source, such asgmail,ms365,imap, orhosted.
6. Map extracted fields with Custom JSON
Use map.custom_json when the receiver needs a field-specific body.
This example migrates a common key/value parser template:
{
"steps": [
{
"name": "map.custom_json",
"args": {
"version": "v1",
"vars": [
{
"name": "kv",
"expr": {
"call.extract.key_value_pairs": {
"mode": "auto",
"html": { "var": "message.html" },
"text": { "var": "message.text" }
}
}
},
{
"name": "plain_text",
"expr": {
"call.transform.html_to_text": {
"html": { "var": "message.html" },
"text": { "var": "message.text" }
}
}
}
],
"output": {
"type": "parser_migration.invoice",
"event_id": { "var": "ctx.event_id" },
"source_type": { "var": "ctx.source_type" },
"message_id": { "var": "message.message_id" },
"vendor_email": { "var": "message.from[0].email" },
"invoice": {
"number": { "var": "vars.kv.values.invoice_number" },
"date": { "var": "vars.kv.values.invoice_date" },
"total": { "var": "vars.kv.values.total" }
},
"subject": { "var": "message.subject" },
"body": { "var": "vars.plain_text" },
"raw_fields": { "var": "vars.kv.items" },
"attachments": { "var": "message.attachments" }
}
}
}
]
}
Use provider-real sample emails before relying on an extraction helper. Parser tools often hide format drift. MailWebhook will emit null for missing paths, so your receiver should validate required fields before creating downstream records.
Full route JSON example
{
"name": "Parser migration invoice intake",
"endpoint_id": "0f5a83e4-8220-4f0d-917e-6be20d9dc32d",
"signing_secret_kid": "route-signing-prod",
"enabled": true,
"rule": {
"to_emails": ["ap@example.com"],
"from_domains": ["vendor.example"],
"subject_contains": ["invoice", "receipt"],
"attachments_mime": ["application/pdf"]
},
"pipeline": {
"steps": [
{
"name": "map.custom_json",
"args": {
"version": "v1",
"vars": [
{
"name": "kv",
"expr": {
"call.extract.key_value_pairs": {
"mode": "auto",
"html": { "var": "message.html" },
"text": { "var": "message.text" }
}
}
}
],
"output": {
"type": "parser_migration.invoice",
"event_id": { "var": "ctx.event_id" },
"message_id": { "var": "message.message_id" },
"invoice_number": { "var": "vars.kv.values.invoice_number" },
"total": { "var": "vars.kv.values.total" },
"raw_fields": { "var": "vars.kv.items" },
"attachments": { "var": "message.attachments" }
}
}
}
]
}
}
map.custom_json must be the final pipeline step.
Attachment migration
Parser tools sometimes pass attachment files directly to another app. MailWebhook sends attachment metadata in the webhook body and stores file bytes separately.
The migration pattern is:
- Include
message.message_idand attachment metadata in your Custom JSON output, or use Generic JSON. - Store the attachment
id, filename, content type, size, and checksum when available. - Request a short-lived download URL from the attachment download API with a MailWebhook API key.
- Fetch the file server-side.
- Upload the file to the downstream system or attach it to the created record.
Keep project API keys out of browser code and client-side logs.
Endpoint behavior
MailWebhook delivers the route pipeline output as a JSON POST request.
Delivery headers include:
X-MailWebhook-SignatureX-Idempotency-KeyContent-Type: application/json- Any custom endpoint headers you configured
The request body is exactly the route pipeline output. MailWebhook does not add another wrapper around the mapper result.
Verify the signature against the raw request body bytes. Use the idempotency key as the duplicate guard for retries and replay.
Cutover plan
Use a staged migration so parser-tool output and MailWebhook output can be compared.
- Keep the old parser workflow active.
- Connect the MailWebhook mailbox source.
- Create the endpoint and a narrow route rule.
- Start with Generic JSON and send provider-real test emails.
- Compare the MailWebhook payload with the old parser output.
- Add a Custom JSON mapper for stable fields.
- Update the receiver to validate required fields and store idempotency keys.
- Move forwarding, mailbox rules, aliases, or application settings to MailWebhook.
- Watch Events and Delivery Attempts History.
- Disable the old parser workflow after downstream records match.
If both paths run at once, make sure the downstream system can dedupe records or point one path at a test destination.
Validation checklist
Before cutover, confirm:
- The selected mailbox source receives a new test email.
- The route rule matches only intended messages.
- Generic JSON payloads have
schema.nameset tomailwebhook.generic. - The Custom JSON mapper saves without validation errors.
- Extracted fields match provider-real sample emails.
- Required missing fields are rejected or queued for review by the receiver.
- The receiver verifies
X-MailWebhook-Signatureagainst raw body bytes. - The receiver stores
X-Idempotency-Key. - Replay of the same event does not create a duplicate record.
- Attachments are fetched server-side through the attachment download API when needed.
Common migration issues
Extracted fields are null
Missing Custom JSON paths evaluate to null.
Common causes:
- The source email label changed.
- The old parser template used a visual selector that does not map to key/value text.
- The email contains HTML tables and should use table extraction or receiver-side parsing.
- The test email is not the same shape as production mail.
Keep raw_fields during migration so you can see which keys the helper found.
The route matches too many messages
Tighten the route rule before changing the mapper.
Use recipient, sender domain, subject terms, attachment MIME type, and none rules to keep unrelated emails out of workflow-specific mappers.
Attachments are listed, but the downstream app has no file
MailWebhook webhook JSON includes attachment metadata. It does not inline file bytes.
Fetch file content through the attachment download API after the receiver has accepted and stored the work item.
Duplicate records appear
Store X-Idempotency-Key before creating downstream records.
MailWebhook may retry retryable failures and can replay an event. Repeated deliveries with the same idempotency key should be treated as the same processing attempt.
Signature verification fails
Verify X-MailWebhook-Signature against the exact raw request body bytes.
Do not verify against parsed JSON, pretty-printed JSON, decoded text, or a reserialized copy of the body.
The old parser handled messy documents better
Keep that parsing in the receiver or migrate those workflows later.
MailWebhook Custom JSON works best when the email structure is stable enough for route rules, normalized fields, extraction helpers, and explicit receiver validation.
Related docs
- Mailboxes
- Connect Gmail as a mailbox source
- Microsoft 365 mailbox setup
- IMAP mailbox configuration
- Create a hosted mailbox
- Rules
- Pipeline
- Generic JSON
- Custom JSON
- JsonLogic-Style DSL
- Custom JSON recipes
- Webhook payload reference
- Endpoints
- Verify signed webhook deliveries
- Webhook retries and replay
- Fetch email attachments from webhook payloads
- API keys and attachment downloads
- Send a test email and inspect the payload
- Troubleshoot Custom JSON mapper errors
- Troubleshoot route rules that do not match
- Troubleshoot failed webhook deliveries