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.2 · MITPublication status #
n8n-nodes-mailmint is published on npm. npm view
n8n-nodes-mailmint version returns 0.1.2. On self-hosted n8n the
Settings → Community Nodes → Install route below works today. n8n published
version 0.1.2 in its verified registry on 1 September 2026, so the same node now installs on
n8n Cloud too. The HTTP Request route remains available when community nodes are disabled.
Every screenshot on this page is the node running in a real n8n instance, not a mock-up.
Install #
Self-hosted n8n
Settings → Community Nodes → Install, then enter n8n-nodes-mailmint. Or
from the command line:
cd ~/.n8n/nodes
npm install n8n-nodes-mailmint
Then restart n8n. The package has zero runtime dependencies and is published from GitHub Actions with an
npm provenance attestation, so npm audit signatures can verify which commit built it.
n8n Cloud
Yes — install n8n-nodes-mailmint from n8n’s verified community-node registry. See
below for the HTTP Request route when community nodes are disabled.
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. The hosted service is
https://mailmint.app.mintapis.com; use that unless you run your own deployment. For a local API with n8n in Docker, usehttp://host.docker.internal:3100. - 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:
https://mailmint.app.mintapis.com.First workflow — mail you already have #
You do not need a MailMint address to start. 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.
Two published workflows #
Rather than building from an empty canvas: Screen invoice emails and hold the doubtful
ones for review is a finished workflow that takes an invoice email as JSON, reads the
fields out of it, and answers approved, review with the reasons, or a
422 with the failure text. Twelve nodes, annotated, with both failure paths wired.
Open the published workflow on n8n Download its JSON
In n8n: Workflows → Import from File, pick the file, then add your MailMint credential to the Read the invoice fields node and activate it. Nothing else needs editing — the policy values live in one Set node at the front.
It was executed before it was published here, against this API: an invoice at 1,284.00 EUR came
back approved with the lowest field confidence at 0.97 and 31 days until
due; 9,900.00 EUR came back review — “9900 is above the 2500 ceiling”;
a message with no invoice in it left by the node’s Needs Review output carrying
missing_required:invoice_number, amount_due, due_date; and with MailMint pointed at a
closed port the caller still got “The service refused the connection” rather than a
hung request.
Both MailMint workflows were approved and published by n8n on 31 August 2026. The second, File supplier invoice emails into Google Sheets with MailMint, starts with IMAP and appends the extracted invoice fields to a sheet. Use the screening workflow when a human approval gate matters; use the Sheets workflow when the destination is a ledger or review list.
What works on n8n Cloud today #
n8n-nodes-mailmint has been in n8n’s verified registry since 1 September 2026 and
installs directly on Cloud. If your organisation disables community nodes, an HTTP Request
node does the same job:
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.