npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

n8n-nodes-mailmint

v0.1.2

Published

Turn inbound email into structured JSON. Define the fields you want right inside n8n, then receive parsed messages by webhook or polling — or parse mail that another node already fetched.

Downloads

259

Readme

n8n-nodes-mailmint

Turn an email into structured JSON — and define the fields you want inside n8n, on the canvas, next to the workflow that consumes them.

Every other email parser makes you leave your automation tool, open their web app, upload a sample, and click on the parts of it you want. Then you go back to n8n and receive whatever they decided to send. This node does the whole thing in the node: name a field, give it a type and a sentence of description, execute, see the value.

MailMint is the API behind it.

Status

Published on npm as n8n-nodes-mailmint, and the API behind it is hosted at https://mailmint.app.mintapis.com — sign up there, copy the mm_live_… key from the dashboard, and the credential's Base URL default already points at it. You can also point it at a MailMint you run yourself.


Install

In n8n: Settings → Community Nodes → Install, enter n8n-nodes-mailmint, confirm.

Self-hosted, from the command line:

cd ~/.n8n/nodes
npm install n8n-nodes-mailmint

Then restart n8n.

The package has zero runtime dependencies — it uses n8n's own HTTP helper and nothing else. It is published from this repository's GitHub Actions workflow with an npm provenance attestation, so npm audit signatures verifies which commit built it.

Credential

  1. In n8n, add a MailMint API credential.
  2. API Key — the key for your MailMint account. It starts with mm_live_.
  3. Base URL — already filled in with https://mailmint.app.mintapis.com, the hosted service. Change it only if you run MailMint yourself; no trailing slash.
  4. Save. The credential tests itself against GET /v1/usage, so you get a green tick before you build anything.

First workflow, in five minutes — 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.

  1. Add an Email Trigger (IMAP) node and point it at your mailbox. Any Format works.

  2. Add a MailMint node after it. It opens on Parse → Parse Email, which is the default, with Input: Automatic — that is already correct. Automatic 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.

  3. Under Fields, click Add Field three times and fill them in:

    | Name | Type | Description | Hint | |---|---|---|---| | invoice_number | String | the invoice or reference number | labelled Invoice | | total | Number | grand total including tax | labelled Total or Amount Due | | due_date | Date | when payment is due | labelled Due |

  4. Execute step.

You get back:

{
  "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": "[email protected]",
    "received_at": "2026-08-25T09:14:03.221Z",
    "type": "invoice",
    "needs_review": false,
    "flags": [],
    "attachment_count": 1,
    "attachment_names": ["invoice.pdf"],
    "has_extracted_attachments": true
  }
}

That is Simplify at work — 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 parse result: headers, both bodies, tables, detected amounts and dates, and per-field confidence with the evidence each value was read from.

The Description column is the single biggest lever on accuracy. Write it the way you would explain the field to a new colleague, and put the label the mail actually uses in the Hint.


The schema editor

Every field you add maps onto a real type, and three of them take a second parameter:

| Type | What you get back | Extra | |---|---|---| | 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". A required field that is missing is still null — it is flagged, not fabricated.

You can also point Schema at From a Mailbox to reuse the schema saved on one of your MailMint mailboxes, or at JSON to compute it in an earlier node.

Line items — one item per row

The most common 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 array field you defined first, then the largest table in the body, then the largest table read out of an attachment — so an invoice whose detail only exists inside the attached PDF still fans out. Name a specific one in Line Items From if you want a particular table.

_row_count travels on every row and _line_items_truncated says so out loud when the table came back short. A message with no rows still produces exactly one item, with _row_count: 0 — nothing is ever silently dropped. (With Simplify off the same item also carries line_item: null and line_item_count: 0.)

It picks the rows carefully: an array of objects first, so a tags or skus field of plain strings is not mistaken for the line items; then tables; and a list of plain strings only when there is genuinely nothing else. If a row has a column with the same name as one of your header fields — a per-line total alongside the invoice total — the row's value wins for that row and the message-level one is kept, named, under _shadowed.

pairedItem is set on every row, so n8n can still trace all forty items back to the one email they came from.

Confidence, and 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. No IF node, no expression.

Line items follow their message — forty rows from a doubtful invoice all go down the same branch together.

Attachments

The filenames, sizes, and anything the parser read out of a PDF or a spreadsheet are on every simplified item already, under _attachments, with no option to turn on:

"_attachments": [
  { "filename": "invoice.pdf", "content_type": "application/pdf", "size": 48213,
    "extracted": { "kind": "pdf", "pages": 1, "text": "…", "tables": [ … ] } }
]

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.


Your own inbound address

For mail that should come to MailMint rather than through your own IMAP:

  1. MailMint → Mailbox → Create, give it a name and a schema. The response carries the address, something like [email protected].
  2. Point whatever sends the mail at that address.
  3. Add a MailMint Trigger to receive the parsed messages.

MailMint Trigger

One node, two modes.

Webhook (the default) 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 it. 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.

Polling is 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 that cursor and emits nothing, so switching a workflow on never floods it with a week of old mail. Fetch Test Event in the editor returns your most recent real message without moving the cursor.

Each trigger registers its own webhook endpoint on the mailbox, with its own signing secret, so two workflows can watch the same mailbox and neither can switch the other one off: deactivating one deletes only the endpoint it created. Against an older MailMint that carries a single webhook_url per mailbox, the node falls back to that field — and refuses to activate if it is already pointing somewhere else, rather than quietly taking delivery away from whatever is listening there.

n8n adds its own Poll Times section to any node that can poll, so it appears in both modes. In Webhook mode it does nothing — ignore it. It cannot be hidden from inside a node.

Fetch Test Event in Polling mode returns your most recent real message without touching the cursor, so you can see the output shape before any mail has arrived. In Webhook mode the same button registers the editor's test URL with MailMint and waits for a real delivery — send a mail to the address and it lands in the editor, signature checked.

Both modes take the same Filters — only messages needing review, only from a given sender or domain, only for one mailbox — and the same Output, Simplify and Route Messages Needing Review Separately settings as the main node.

Operations

| 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 worth knowing about. Zapier's answer to "can I replay old mail" is "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 it directly.

Errors

Every failure carries the API's own message and its hint, plus what to click in n8n, and 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.

A note on auth.dkim

_meta.dkim passes the API's value straight through, and it has three meaningful states, not two: pass, fail, and body_altered. body_altered means the message was signed correctly and then modified afterwards — which is what forwarding, mailing lists and corporate link-rewriting gateways all do to perfectly legitimate mail. Branch on _meta.dkim === 'fail' when you want forgeries; that will not catch your colleague's forward.

Licence

MIT