MailMint for n8n
Every other email parser makes you leave n8n, open their web app, upload a sample and click on the parts of it you want — then come back and receive whatever they decided to send. This node does the whole thing on the canvas: name a field, give it a type and a sentence of description, hit Execute step, see the value.
n8n-nodes-mailmint · v0.1.0 · MITPublication status #
n8n-nodes-mailmint has never been published to npm. npm view
n8n-nodes-mailmint returns a 404 today. That means the Settings → Community Nodes →
Install route below does not work yet, and n8n Cloud — which only accepts verified
community nodes — cannot install it at all.
Every screenshot on this page is the node running in a real n8n instance from a local build, not a mock-up. The install instructions are here because they are what will be true the moment it is published, and they are marked as not-yet rather than quietly presented as working. What to do instead, today.
Install #
Today — build the tarball and install that
Until the package is published this is the honest route, and it is the one every screenshot on this page was taken through:
npm install --ignore-scripts
npm run build
npm pack # -> n8n-nodes-mailmint-<version>.tgz
cd ~/.n8n/nodes
npm install /path/to/n8n-nodes-mailmint-<version>.tgz
Then restart n8n.
Once it is published
Settings → Community Nodes → Install, then enter n8n-nodes-mailmint; or
cd ~/.n8n/nodes && npm install n8n-nodes-mailmint from the CLI. Neither works yet — see
above.
Once installed, both nodes appear in the node panel under their own names. The trigger is found by searching the trigger panel for MailMint:
The credential #
- In n8n, add a MailMint API credential.
- API Key — the key for your MailMint account. It starts with
mm_live_. - Base URL — the root URL of the MailMint API you are talking to, with no trailing
slash. There is no default, because there is no hosted MailMint to default to. Use your
own deployment, or
http://host.docker.internal:3100when the API runs locally and n8n is in Docker. - Save.
The credential tests itself against GET /v1/usage, so you get a real green tick before you
build anything rather than an “untested” badge:
First workflow — mail you already have #
You do not need a MailMint address to start, which matters, because the hosted address is not live yet. If mail is already reaching n8n — through the built-in Email Trigger (IMAP), through Gmail, through anything — put a MailMint node after it and it parses what arrives.
- Add an Email Trigger (IMAP) node and point it at your mailbox. Any Format works.
- Add a MailMint node after it. It opens on Parse → Parse Email with Input: Automatic, which is already correct.
- Under Fields, click Add Field three times and fill them in.
- Execute step.
What “Automatic” actually does. It takes the raw .eml off the
binary field when the IMAP node is set to RAW, and the subject / textPlain /
textHtml fields off the JSON when it is set to Resolved or Simple. It will not mistake an
attached PDF for the message. The other Input modes — Binary File, Raw MIME
Text and Fields — are there for when you want to be explicit.
The schema editor #
This is the part that does not exist anywhere else. Each field you add has five inputs:
| Input | What to put in it |
|---|---|
| Name | The JSON key you want back, e.g. invoice_number. |
| Type | One of thirteen. See the table below. |
| Description | The single biggest lever on accuracy. Write it the way you would explain the field to a new colleague — “grand total including tax”, not “total”. |
| Hint | The label the mail actually uses: “labelled Total or Amount Due”. |
| Required | A toggle. A required field that is missing still comes back
null — it is flagged, never fabricated. |
| Type in the dropdown | You get back | Extra input |
|---|---|---|
| String · Email · URL · Phone Number | the text as written | |
| Number · Integer | a number | |
| Currency | { "amount": 31.5, "currency": "USD" } | |
| Date | 2026-09-08 | |
| Date and Time | ISO-8601, UTC | |
| Boolean | true / false | |
| Enum | one of your values, or null | Options, comma separated |
| Array | a list | Item Type |
| Object | a nested object | Sub-Fields, a JSON array of field definitions |
A value that cannot be coerced comes back as null with a type_error flag.
It is never invented, and never a guess string like “N/A”.
Schema also takes From a Mailbox, to reuse the schema saved on one of your MailMint mailboxes, and JSON, to compute the whole schema in an earlier node.
What comes out #
_needs_review beside them, and
everything else under _meta.{
"invoice_number": "INV-2291",
"total": 31.5,
"due_date": "2026-09-08",
"_needs_review": false,
"_meta": {
"subject": "Invoice INV-2291 from Acme Ltd",
"from_email": "billing@acme.example",
"received_at": "2026-08-25T09:14:03.221Z",
"type": "invoice",
"needs_review": false,
"flags": [],
"attachment_count": 1,
"attachment_names": ["invoice-2291.pdf"]
}
}
That is Simplify, which is on by default, because the next node is nearly always a Google Sheets, a Postgres or an IF. Turn it off and you get the full message object: headers, both bodies, tables, detected amounts and dates, and per-field confidence with the evidence each value was read from.
Line items — one item per row #
The loudest complaint in this whole category is “the mail had forty rows and I got one”. Set Output to One Item Per Line Item and one email becomes one n8n item per invoice line, with the header fields repeated on each.
{ "invoice_number": "INV-2292", "total": 132,
"Item": "Widget", "Qty": "3", "Amount": "$27.00",
"_row_index": 0, "_row_count": 3, "_line_items_truncated": false }
- It finds the rows on its own: an
arrayfield you defined first, then the largest table found in the body. Name a specific one in Line Items From if you want a particular table. _row_counttravels on every row and_line_items_truncatedsays so out loud when the table came back short. A short array can never be silent.- A message with no rows still produces exactly one item, with
_row_count: 0. Nothing is ever silently dropped. pairedItemis set on every row, so n8n can still trace all forty items back to the one email they came from.
For comparison: Zapier’s documented answer to the same requirement is to hand-write
{{shotnumberOne}}…{{shotnumberN}} against a hard cap of 15 templates, and Make
needs an Iterator plus an Array Aggregator and breaks past two columns.
Routing what needs a human #
Every field carries a confidence, the source it came from and the verbatim evidence it was read out of. Turn on Options → Include Confidence:
"_confidence": {
"total": { "confidence": 0.97, "source": "rule+llm", "evidence": "Total: $31.50" }
}
_needs_review sits at the top level of every simplified item. Turn on Route Messages
Needing Review Separately and the node grows a second output: anything with a missing required
field, a low-confidence value, a type it could not coerce, or evidence it could not find in the mail goes
down Needs Review instead of Parsed.
Line items follow their message: forty rows from a doubtful invoice all go down the same branch together, rather than being split across two.
The same setting exists on the trigger, so mail that needs a human can be routed the moment it arrives.
Attachments #
Filenames, content types, sizes and checksums are on every simplified item already, under
_attachments, with no option to turn on. The bytes are the one thing behind a switch
(Options → Include Attachment Bytes), because a single PDF is usually larger than the
rest of the message put together. To get a file as real n8n binary data, use
Message → Download Attachment.
Reading text out of a PDF is built but not yet wired in. The node knows how to fan
line items out of _attachments[].extracted, the API reserves that slot, and the extractor
itself exists — but nothing connects them yet, so the slot is empty today. An invoice whose detail
lives only inside the attached PDF gives you the file and its metadata, not its contents.
MailMint Trigger #
For mail that should come to MailMint rather than through your own IMAP. One node, two modes.
Webhook — the default
It registers itself. On activation it sets the mailbox’s webhook_url to this
workflow’s URL and installs a signing secret it generates for you; on deactivation it clears them.
Every incoming delivery has its x-mailmint-signature HMAC verified before the workflow runs — a
body that does not verify is answered 401 and never starts anything. Instant, and there is
nothing to configure beyond picking the mailbox.
Options → Signature Tolerance (Seconds) defaults to 300, and Options → Webhook Secret lets you pin your own instead of letting the node generate one.
spf, dkim and dmarc sit in _meta.Polling
For an n8n the internet cannot reach. It reads GET /v1/events on
your poll schedule and remembers the cursor on the node. On first activation it seeds the cursor and
emits nothing, so switching a workflow on never floods it with a week of old mail.
Filters
Both modes take the same Filters — Needs Review Only, From Sender (an address or a domain) and Mailbox Name or ID — and the same Output, Simplify and Route Messages Needing Review Separately settings as the main node.
All eleven actions #
| Resource | Operation | What it does |
|---|---|---|
| Parse | Parse Email | Extract your fields from an email the workflow already has. Nothing is stored. |
| Message | Get | One parsed message by ID |
| Get Many | List parsed messages, filtered by mailbox, date or needs-review | |
| Get Raw | The original RFC822 message as binary .eml | |
| Download Attachment | One attachment as binary data | |
| Reparse | Re-run one message, optionally against a different schema | |
| Mailbox | Create | A new inbound address with a schema on it |
| Get Many | The addresses on your account | |
| Update | Name, schema, webhook URL, webhook secret | |
| Reparse Messages | Re-run every stored message after you fix the schema, with a Dry Run first | |
| Delete |
Reparse Messages is the one worth knowing about. Zapier’s answer to “can I replay old mail” is verbatim “there is no way to replay them”, and Mailparser stops at the last 300. Fix your schema, dry-run it to see what would change, then run it for real. Re-delivery is a separate switch from re-parsing, so nobody accidentally fires a month of webhooks at their own workflow.
The main node is marked usableAsTool, so an n8n AI Agent can call MailMint
directly as a tool.
Errors #
Every failure carries the API’s own message and its hint, plus the item index that failed. With Settings → Continue On Fail on, the failing item becomes:
{ "error": { "code": "message_not_found", "message": "…", "hint": "…", "httpCode": "404" },
"errorMessage": "…" }
so an IF node can branch on {{ $json.error.code }} instead of matching on a sentence. The
full code list is in the API reference.
What works on n8n Cloud today #
n8n Cloud only accepts verified community nodes, and n8n-nodes-mailmint is not published at
all yet, let alone verified. Until it is, an HTTP Request node does the same job from any
n8n:
Method: POST
URL: {{ $env.MAILMINT_URL }}/v1/parse
Authentication: Generic → Header Auth
Name: Authorization
Value: Bearer mm_live_…
Send Body: on (JSON)
Body:
{
"subject": "{{ $json.subject }}",
"text": "{{ $json.textPlain }}",
"html": "{{ $json.textHtml }}",
"schema": [
{ "name": "invoice_number", "type": "string", "description": "the invoice or reference number" },
{ "name": "total", "type": "number", "description": "grand total including tax" }
]
}
You lose the schema editor, the line-item fan-out and the second output — everything else, including the confidence and evidence on every field, is identical, because the node is a thin client over the same endpoint.