Back to Browse

Mailwarden MCP Server

by Csitte
Developer ToolsModerate6.2MCP RegistryLocal
Free

Server data from the Official MCP Registry

Gmail MCP server for email triage: search, labels, archive, trash, unsubscribe, snooze.

About

Gmail MCP server for email triage: search, labels, archive, trash, unsubscribe, snooze.

Security Report

6.2
Moderate6.2Moderate Risk

Valid MCP server (2 strong, 3 medium validity signals). 5 known CVEs in dependencies (0 critical, 3 high severity) Package registry verified. Imported from the Official MCP Registry. Trust signals: 3 highly-trusted packages.

4 files analyzed · 6 issues found

Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.

Permissions Required

This plugin requests these system permissions. Most are normal for its category.

file_system

Check that this permission is expected for this type of plugin.

env_vars

Check that this permission is expected for this type of plugin.

What You'll Need

Set these up before or after installing:

Config directory (default ~/.mailwarden), holds credentials.json and token.jsonOptional

Environment variable: MAILWARDEN_DIR

Path to the Google OAuth credentials.json (Desktop app client)Optional

Environment variable: MAILWARDEN_CREDENTIALS

Set to 1 to sweep due snoozes at startup and hourly while runningOptional

Environment variable: MAILWARDEN_AUTO_SWEEP

Restrict download_attachment and export_thread writes to this directory (recommended for HTTP hosting)Optional

Environment variable: MAILWARDEN_DOWNLOAD_DIR

How to Install

Add this to your MCP configuration file:

{
  "mcpServers": {
    "io-github-csitte-mailwarden": {
      "env": {
        "MAILWARDEN_DIR": "your-mailwarden-dir-here",
        "MAILWARDEN_AUTO_SWEEP": "your-mailwarden-auto-sweep-here",
        "MAILWARDEN_CREDENTIALS": "your-mailwarden-credentials-here",
        "MAILWARDEN_DOWNLOAD_DIR": "your-mailwarden-download-dir-here"
      },
      "args": [
        "-y",
        "mailwarden"
      ],
      "command": "npx"
    }
  }
}

Documentation

View on GitHub

From the project's GitHub README.

mailwarden

npm license Node Website Smithery

A reliable, native Gmail MCP server — full mailbox triage for AI assistants, with the feature no other Gmail MCP server ships: mailbox-side snooze.

Highlights

  • Snooze — the only mailbox-side snooze in a Gmail MCP server. Archive a thread now, have it resurface in the inbox on a date. Built on dated labels + a sweep, so it works from any client, is visible in Gmail itself, and survives restarts. (Where another server offers a "snooze", it is a local reminder list — the mail never leaves or re-enters the inbox.)
  • Search you can trust. Gmail's threads.list — the call any thread search goes through — can answer is:unread from a stale thread-level read state: measured in one real mailbox, 86% of the threads it returned held no unread message at all; in a second mailbox, no drift whatsoever. You cannot tell which mailbox you are in without looking, so search re-verifies every hit against its live labels. Paginated via pageToken/nextPageToken. (the measurements)
  • Sender authentication, not sender spelling. get_thread reports the SPF/DKIM/DMARC results the receiving server recorded, so "is this really from my bank?" is answered from the message's own headers instead of from how the domain looks. It reads Gmail's own report only — the one written under the id mx.google.com; a message can carry forged ones of its own — says whether a passing DKIM signature belongs to the From domain, and says unchecked when nobody checked, because a missing check is not a passing one.
  • Bulk operations that scale. bulk_modify archives/labels everything matching a query at 1000 messages per API request — with per-chunk partial-success reporting instead of all-or-nothing. The snooze sweep uses the same batch path.
  • Structured outputs. Every tool declares an outputSchema and returns validated structuredContent alongside fenced JSON text — no parsing guesswork for clients. Failures are structured as well: a code and a retryable flag, so a client can tell "try again later" from "re-authorize" without reading prose.
  • Small attack surface. No send tools (no exfiltration path for prompt-injected mail), optional read-only mode, no telemetry, no open ports by default, symlink-safe download fencing, injection-fenced output. And no code path that could send: every Gmail request passes an egress checkpoint that refuses messages.send, drafts.send, permanent deletion and forwarding settings, whatever a compromised or careless caller asks for — and every other draft endpoint too, unless you switch on the opt-in drafts tier, which saves drafts for you to send yourself. One deliberate exception: unsubscribe / bulk_unsubscribe (manage tier) contact the opt-out endpoint named in a message's own header — the only non-Google host mailwarden ever reaches, and a read-tier deployment makes no outbound request at all. Details under Security & privacy and Unsubscribing.
  • Correct with real-world mail. RFC 2047 headers decoded (=?UTF-8?B?…?= → readable text), bodies decoded by their bytes and their declared charset (text Gmail already converted to UTF-8 is read as UTF-8 whatever the header says, anything else in the charset it names; no mojibake for ISO-8859-1/Shift_JIS mail), 429/5xx retried with exponential backoff.

Why

Connectors that sync or cache your mailbox can lag behind it — and even Gmail's own search index is sometimes loose (see below). mailwarden talks straight to the live Gmail API (no cached snapshot) and re-verifies what the index returns, so what you see is what's actually there. It's a generic Gmail capability layer — keep your own rules/logic in your AI client, not in the server.

search goes one step further than the raw API: Gmail's threads.list index can answer read-state operators from a stale copy of that state, so is:unread returns threads you finished reading weeks ago — in one measured mailbox, the large majority of what came back. Since every hit is fetched live anyway, search re-checks the unambiguous predicates (is:unread/is:read/is:starred/in:inbox/category:…, with negation) against each thread's true labels and drops the index's false positives.

Compared to other Gmail MCP servers

Most Gmail MCP servers cover the same read/label/send surface. Two capabilities are still unique to mailwarden as far as we have looked (mailbox-side snooze, search re-verification), and one deliberate omission is a security feature, not a gap. A third, sender authentication, was unique when it shipped and no longer is: @agentcomms/gmail-mcp has read Authentication-Results since 18 September 2026, trusting only the report whose authserv-id is mx.google.com. It has its own column below since 13 October 2026. Google's own server is also narrower than it looks: draft-only, and no trash, filters or unsubscribe.

CapabilitymailwardenGoogle officialtaylorwilsdona-bonusaaronsbklodragentcommsgmail-tidy
Mailbox-side snooze — archive now, resurface in the inbox on a date/time or preset✅———————
Search-result re-verification — drops the thread index's false positives against live labels✅———————
Sender authentication — SPF/DKIM/DMARC as the receiving server recorded them, on every message✅ Gmail's own report only, values token-validated, DKIM alignment—————✅ Gmail's own report only, DKIM alignment⚠️ fail values from the first header, issuer unchecked
Sweep / bulk over a query — one action across every thread a search returns✅ 1000/req, partial-success—⚠️ batch by explicit ids; filter apply relabels over a query, no re-check—✅ previewed query, up to 10,000, no re-check⚠️ batch by explicit ids⚠️ batch by explicit ids, exact per-message undo⚠️ over a query, capped at 5,000, no preview or re-check
Unsubscribe — per-sender overview + RFC 8058 one-click opt-out, no send scope needed✅—⚠️ header shown, no action————⚠️ link shown, no action
Inbox triage overview — one call that buckets what is waiting✅ sender/label/age + header signals——✅ heuristic flags + stats⚠️ unread list, no buckets——⚠️ sender stats + newsletter list, no buckets
Server-side filters — rules that keep triaging with no assistant in the loop✅ never forwarding—✅—✅ previewed; ⚠️ forwarding allowed unless draft-only policy✅ never forwarding—✅ never forwarding or trash
No send tools — by design — a prompt-injected mail has no exfiltration path✅ no compose by default; ⚠️ opt-in draft-only tier⚠️ draft-only❌ sends❌ sends❌ sends; ⚠️ opt-in draft-only policy❌ sends❌ sends; ⚠️ per-message approval, in chat by default⚠️ draft-only
Least-privilege tool tiers — OAuth scopes derived from the tools you enable✅⚠️ scope split⚠️ --permissions narrows scopes per service (cumulative or +-union); tiers narrow tools only—⚠️ per-account read/readwrite, at call time⚠️ inverse: tools gated by granted scopes⚠️ tier per mailbox picks the scopes; writes checked against the grant at call time—
Token encryption at rest✅ AES-256-GCM by default, key in the OS keystore; a passphrase where there is nonen/a (hosted)⚠️ file mode 0600; bucket CMEK on GCS———✅ OS keychain by default; owner-only file by choice✅ AES-256-GCM by default, key in the OS keystore
No vendor cloud — you operate the server✅❌ Google-hosted✅✅✅✅✅✅
Structured outputs — every tool declares an outputSchema✅✅ documented per tool in its reference———⚠️ two tools (download_email, download_all_attachments), more planned✅ every tool—

Snapshot as of 13 October 2026 — the oldest of the seven column checks (Google's reference and a-bonus, both last read that day), and so the only date the table as a whole can claim. Each repository column is brought forward by reading the diff against the revision recorded in docs/comparison-sources.json whenever npm run table-sources reports a move. On 25 October 2026 four of them moved at once. aaronsb went to 9c9f8033 (v4.8.0), and two of its cells with it: since 16 October 2026 it applies a label change to everything a query matches, behind a preview that counts the set, keeps the ids on the server and hands back a token valid once for thirty minutes (up to 10,000 messages; the matches are not re-checked), and it creates, updates and deletes filters behind the same kind of preview — forwarding included unless its draft-only policy is set. taylorwilsdon went to fcbf5a2c (v2.2.0); --permissions now also takes a union such as gmail:readonly+send that grants only the named levels, which the least-privilege cell now names. klodr went to e5c080c3 (3.0.0); create_filter now refuses a forwarding action whatever the scopes, and its filter cell says so. agentcomms went to 261cae4f (0.28.0, with per-recipient rules on what it sends); no cell moves. a-bonus has not moved since 13 October 2026. Google's tool reference, last read on 13 October 2026, documents an output schema on every one of its eleven tools; it is still Developer Preview, with no send, trash, filter, snooze, unsubscribe or query-driven tool. — = not offered / not documented. Columns are the servers a reader is most likely to reach for — Google's first-party one, plus the three largest community servers still under maintenance — and klodr, which comes closest to mailwarden's own least-privilege design. agentcomms (@agentcomms/gmail-mcp, read at 01cee751 on 13 October 2026) was added the same day for two reasons: it is the only other server we know of that reads Authentication-Results, and it pins that read to Gmail's own report, which is stricter than what mailwarden did until then and the reason mailwarden now does the same; and its npm reach overtook klodr's within a month of its first release. Its bulk path is per id, not per query, but every change returns an exact per-message undo — the design mailwarden's bulk_modify adopted as undo: true. It sends, behind a per-message approval whose default is a yes in the conversation; its own README says the software cannot tell that yes from the agent's, and offers a terminal confirmation (confirm) or no sending at all (never) instead. gmail-tidy (gmail-tidy-mcp, read at 6df48c69 on 25 October 2026) was added that day because its stated position is the closest to this one: no send tool and no trash, a build test that fails on either, and filters that can neither forward nor trash. It does write drafts, which puts it on the send row next to Google's server rather than next to this one — its own README says plainly that its token could send. It was ahead of mailwarden on one row, and mailwarden has since taken the same design over: it encrypts the token by default, with the key in the OS keystore. Its bulk runs over a query, capped at 5,000 messages, with neither a preview nor a re-check, and it reads SPF/DKIM/DMARC failures from the first Authentication-Results header without asking which server wrote it. Its npm reach matched mailwarden's within ten days of its first release. aaronsb was added on 8 September 2026, and the reason it was missing is worth stating plainly: the selection had been made by capability overlap, so a server an order of magnitude larger than the smallest column sat outside the table while that column stayed in it. It earns its place on the send row rather than on size. It was the first server here besides mailwarden that can be made to refuse sending at all — GWS_SAFETY_POLICY=draft-only-email blocks send, reply, replyAll and forward; agentcomms has since joined it with a never send policy — and the difference that keeps the row honest is where the promise lives: there, sending is the default and the refusal is an operator's environment variable; here, no tool to send was ever written. Its no-delete policy stands in the same relation to this project's trash-only rule. The filter row asks the same question from the other end, and a third project shows why it is one: c0webster/hardened-google-workspace-mcp, a fork of the taylorwilsdon column made for no reason but this one, removes create_gmail_filter and delete_gmail_filter outright because a filter can forward — which is exactly the risk, answered by dropping the capability. mailwarden keeps the capability and removes the risk instead: create_filter has no way to express a forward, and list_filters surfaces the ones that already exist so they can be audited. That fork also shows where a promise of this kind usually leaks, which is why the row above is about the tool surface and the egress guard rather than about scopes: it drops gmail.send but keeps gmail.compose — commented "for draft creation/editing only (NOT sending)" — and gmail.modify. Google documents the first as "Manage drafts and send emails" and the second as "Read, compose, and send emails", so the promise does not rest on one scope too many but on two, and gmail.modify alone would be enough — the same scope this project holds in its own manage tier, which is exactly why the row points at the tool surface. Its token can send; only its tool list cannot. The most installed Gmail server is absent for that reason and not by oversight: GongRzhe/Gmail-MCP-Server is archived, its last commit dating to August 2025, and it still drew 112,163 npm downloads in the month to 29 August 2026. Reach and currency are different questions, and a comparison of what a server does today can only answer the second. Send capability is listed as a security property: mailwarden's lack of it is intentional (see Security & privacy). The encryption row asks who holds the key: mailwarden keeps a random key in the OS keystore by default, as gmail-tidy does, and falls back to a passphrase you set where no keystore is reachable — and to a plaintext file where you set none, which is why the cell names both; taylorwilsdon relies on file permissions locally and on the storage bucket's own CMEK when hosted on GCS — protection against a stolen file in the first case, against a stolen disk in the second. The last row asks who operates the server, not where it happens to run: self-hosting is common ground here, and every community server on this table offers some remote deployment except klodr and gmail-tidy (stdio only) — mailwarden via --http, taylorwilsdon over streamable HTTP with OAuth 2.1, a-bonus on Cloud Run. Running one of them on your own host is not a cloud copy; running it on the vendor's is.

The moat isn't any single row — it's snooze + live re-verification together: an actual inbox-workflow layer that acts on the mailbox's current state, not a cached snapshot. Where others have caught up it's noted honestly above: at-rest encryption (taylorwilsdon; by default and with the key in the OS keystore, agentcomms and gmail-tidy, whose design mailwarden has since taken over), scope-driven tool gating (klodr inversely; taylorwilsdon in our direction, and further than this row said until it was corrected — besides --read-only, which switches the OAuth flow to the read-only scope map, he has had per-service permission levels since 25 February 2026: --permissions gmail:organize builds the requested set from cumulative levels — readonly, organize, drafts, send, full — and his tool registry then disables every tool whose declared scopes fall outside them, deliberately skipping scope-hierarchy expansion so that organize really does exclude gmail.send. What separates that from this row's claim is the direction, not the strength: the level is picked per service rather than derived from the tools you enable, so --tool-tier core --tools gmail still asks for the full Gmail scopes unless a level is named, and neither mode has a guard at the request itself. Our August round read only the tier path and understated him here from 26 August on; re-read in his auth/permissions.py, auth/scopes.py and core/tool_registry.py at 54b1c56 on 7 September 2026 — the day we read, at the revision his repository stood at. The tier half was checked on 26 August 2026 and corrected the same day by csitte.at, who verified it against their own clone rather than taking our word for it), a richer per-message triage heuristic (a-bonus), and bulk organize over a mailbox (the hosted mcpemails.com, which has no snooze either). What none of them do is act on a query and check the mailbox's answer before acting on it — taylorwilsdon's filter apply, aaronsb's previewed modifyMatching and gmail-tidy's organize_emails now do the first half, all over messages.list, and none of them the second.

Running it next to a Workspace server

mailwarden is a Gmail server, not a Workspace suite — if you want Calendar, Drive, Docs and Sheets from one place, a broad server like taylorwilsdon/google_workspace_mcp covers ground this one never will, and the two are not mutually exclusive. Adding both is a reasonable setup, and the reason to is the token, not the tool count: a suite server that can send mail holds a credential that can send mail, for every mailbox it is pointed at. Giving Gmail to mailwarden instead means the mail half of your setup has no compose, reply, forward or send tool at all — or, with the opt-in drafts tier, tools that save drafts and none that send them. Where that promise rests differs by tier, and the distinction matters: on read Google enforces it at the token (gmail.readonly, which the send endpoints reject), while on manage it rests on the tool surface — Gmail does accept gmail.modify for sending, so the scope alone is no guarantee. In both cases the egress guard refuses messages.send and drafts.send in the server itself, and every other draft endpoint unless the drafts tier is on, so an injected message in your inbox has no tool to reach for and no endpoint to reach.

Practical shape: point the suite server at the services you want and disable its Gmail tools (--disabled-tools, or a tier that omits them), and run mailwarden alongside for mail. Keep the tier rule in mind — at most one mailbox per client config should carry writing tiers.

Why re-verification matters — a concrete case

Ask an assistant to "archive the unread promotional mail that's already skipped my inbox" and it will reach for the obvious query, category:updates is:unread -in:inbox. A server that trusts Gmail's index now archives threads you had already read — mail you never meant to touch, gone in a bulk action you can't easily reverse.

Measured, not asserted. In one real mailbox (~70,000 messages), category:updates is:unread returned 131 threads through threads.list, and only 17 of them held an unread message — 87% stale. The same query, same mailbox, same minute, asked through messages.list instead: 19 messages, none stale. So this is not "Gmail search is unreliable" — the thread view of read state lags while the per-message view does not, and search goes through threads.list. A second mailbox, measured identically on the same day, drifted not at all.

Method, all three queries, the controls, and what the finding is not (it is not the index dropping the predicate, and not a quirk of exotic operator combinations): Gmail's thread index can answer is:unread from a stale read state — a standalone report, every figure traced to a recorded measurement.

Which is the whole point: a server cannot know which kind of mailbox it is in. Re-verification costs nothing where nothing drifts, and saves you where it does — in the measurement above, every thread search dropped was genuinely read, and it discarded no genuinely unread mail.

Where it is not free: the bulk tools. search re-verifies because it fetches every hit anyway; bulk_modify (and create_filter's applyToExisting sweep) is sized in thousands of messages, where one fetch per hit is a different order of cost. Both act on what the index returns — and bulk_modify reports unverifiedPredicates, the conditions from your query that were taken on the index's word (+UNREAD, -INBOX, …). Empty means there was nothing to distrust. Non-empty and the result has to be read-state-precise? Resolve the set with search first and act on those thread ids. A dryRun does not close this gap: it re-reads the same index, so it confirms how big the set is, never whether it is right. What its previewToken does close is the other gap, between showing a set and changing one: without it, the write runs the query again and changes whatever matches by then.

The filter sweep does not report that field, deliberately. Its query is not yours — create_filter builds one from the criteria, parenthesising a query criterion and double-quoting every from/to/subject value, and both of those switch the predicate derivation off. The list would come back empty for every filter you can define, and an empty list here would read as "nothing to distrust" when it means "could not tell". So the caveat stays in prose, where it holds without exception: treat a sweep as acting on the index's answer, whatever it matched. Use verify: true to learn what the sweep actually changed, and search when the set has to be right before anything is written.

A cheaper half-measure, honestly labelled. bulk_modify also takes crossCheck: true, which asks Gmail the same question a second way before writing: every derived predicate is re-run as a label filter (labelIds) rather than as a query operator, and any message the two routes disagree about is left untouched and reported. The cost is one extra list call per predicate — flat, independent of how many messages match — where re-verification costs one fetch per hit. What it buys is bounded and worth stating plainly: a disagreement is real evidence, agreement is none at all, because both routes read the same index and an index can be consistently wrong. So unverifiedPredicates still reports what it always did, cross-check or not, and only search re-checks against the mailbox itself. Whether the two routes ever diverge in practice is unmeasured — node scripts/probe-crosscheck.mjs measures exactly that in your own mailbox, read-only and ids only.

mailwarden fetches every hit live anyway, so search re-checks the unambiguous predicates (is:unread, is:read, in:inbox, category:…, with negation) against each thread's true labels and drops the index's false positives before any tool sees them. The bulk action then runs on exactly the set you asked for. This is the difference between acting on what Gmail indexed and acting on what's actually in the mailbox right now — and it's why snooze/sweep are safe to hand to an assistant: the sweep resurfaces only threads whose snooze is genuinely due, verified against live labels at run time.

See it yourself — no Gmail account needed. From a clone of the repo (the demo is a repo-only verification script, not part of the npm package):

git clone https://github.com/csitte/mailwarden && cd mailwarden
npm install && npm run build
node scripts/demo-reverify.mjs

There is a second script next to it, node scripts/probe-reverify.mjs, which measures the same thing in your mailbox instead of a fake one — read-only, metadata only (no subject, sender or body is fetched), printing counts and label names. It is how the numbers above were produced, and how you can check whether your mailbox drifts at all.

The demo drives the real search() against a fake Gmail API whose index is deliberately stale (returns a read thread for an is:unread query, exactly as Gmail does) and shows mailwarden dropping the false positive. It asserts the outcome, so it exits non-zero if the behavior ever regresses. The same case is locked by unit tests in test/gmail.test.ts ("drops index false positives via live-label re-verify").

Asking again without asking for everything

A recurring check — what came in since I last looked — is the expensive shape for a live server: the obvious way to answer it is to search the whole slice again and compare. what_changed (read tier) answers it from Gmail's own event log instead. Hand it the historyId a previous call or get_profile returned, and it comes back with what arrived, what left, and which labels went on or came off, plus the next id to keep.

This is not a cache, and the distinction is the whole design. The only thing that persists between calls is one number, and it persists in the caller. mailwarden still stores nothing about the mailbox, keeps no mirror and no index, and every call remains live against the Gmail API — the same rule as everywhere else here.

Two properties worth knowing before relying on it. It reports events, not state: a message marked unread and then read appears under both, and both are true — for how the mailbox looks now, ask search. And Gmail keeps roughly a week of history, after which an id is refused; mailwarden turns that refusal into an error rather than an empty result, because nothing changed and I can no longer tell you what changed call for opposite reactions and only one of them is safe to act on.

Judging a sender — what authentication answers, and what it doesn't

Every message from get_thread carries an authentication object: the SPF, DKIM and DMARC results the receiving server recorded, plus the domains each check actually validated.

{
  "spf": "pass",   "mailedBy":   "forwarder.example",        // envelope sender SPF checked
  "dkim": "pass",  "signedBy":   "authority.example",         // the signature DMARC can pass on
  "alsoSignedBy": ["forwarder.example", "esp.example"],      // further signatures that passed
  "dmarc": "pass", "headerFrom": "authority.example",        // the From domain DMARC evaluated
  "dkimAligned": true,                                       // a passing signer is the From domain
  "authservId": "mx.google.com",                             // who asserts all of the above
  "returnPath": "bounces+…@forwarder.example"
}

Read dmarc first. It is the only one of the three that ties a passing check to the From address a human sees. spf: "pass" on its own says an envelope sender was authorised to send — something a lookalike domain gets in minutes.

The domains do not have to match, and a mismatch is not a finding. The object above has the shape of a real receipt, forwarded through a custom domain on a mail-routing service before it reached the mailbox. Forwarding rewrites the envelope sender (mailedBy and returnPath become the forwarder) and adds the forwarder's own DKIM signature, while the sender's signature travels on intact behind it — so DMARC still passes, and headerFrom still names the original sender. That is why DMARC, not SPF, is the check that carries meaning here. Treat the domains as the explanation of a result, not as a test of their own.

Several signatures: the one that counts is reported. A message often carries more than one DKIM signature — the sender's, its mail provider's, a forwarder's. signedBy is the passing one aligned with the From domain wherever there is one, because that is the one DMARC passes on; alsoSignedBy names the other domains whose signature also passed, which is where a forwarder shows. Through 0.25.0 the first signature was reported, and a re-signing forwarder puts its own in front: in a mailbox that receives through Cloudflare Email Routing, 156 of 167 received messages read as dkimAligned: false although each carried an aligned, passing signature further down (measured 30 October 2026, see docs/measurements.json).

What a pass does not mean. That the mail really came from that domain — not that the domain deserves anything. A phisher holds perfect SPF, DKIM and DMARC on the lookalike domain he registered this morning; authentication tells you who sent it, and the answer can be "exactly who it claims to be, and that is the problem".

dkimAligned answers the question a signature raises. Anyone can sign mail with a key for a domain they own, so dkim: "pass" alone says only that someone signed. dkimAligned says whether that someone is the From domain, or a parent or child of it. For two sibling subdomains it says nothing: news.bank.example and mail.bank.example are aligned, a.co.uk and b.co.uk are not, and without a public-suffix list the two cases look the same — so the field is absent rather than wrong. false means that no passing signature belongs to the From domain — a forwarder that altered the message (a mailing list adding a footer breaks the sender's signature) leaves only its own. It is absent when DKIM did not pass. As with the domains, false is an explanation, not a verdict — dmarc remains the check to read first.

What unchecked: true means. The message carried no Authentication-Results header from Gmail — nobody looked. It is not a failure, and it is not a pass. The header is written by a server that receives a message, so anything that never arrived from outside — your own sent mail, for instance — should be expected to have none.

Forged reports. A message can carry Authentication-Results headers of its own — an attacker writes whatever he likes into the mail he sends. Only Gmail's own report is read: the topmost one whose authserv-id is mx.google.com, the trust boundary RFC 8601 tells a reader to rely on. A report under any other id is never read, however convincing it looks, and a message that has none from Gmail is unchecked even if it carries a forged one under another id. The limit: mail that reached the mailbox without passing Gmail's inbound mail server — fetched from another account over POP, or inserted by an app — may carry no Gmail report at all, and a forged one that claims mx.google.com is then indistinguishable from the real thing. Mail Gmail received itself always carries its own report on top, so there a forgery is never read; otherReports counts every report that was not read. (Through 0.22.1 the first report was read whatever its id — right wherever Gmail had written one, and wrong exactly where it had not.) Values are validated as tokens rather than passed through, so a field that reads like a verdict cannot carry a sentence. If two results for the same method disagree — a second DKIM signature that failed — the disagreement shows up in alsoReported instead of being swallowed.

Tools

Documentation truncated — see the full README on GitHub.

Reviews

No reviews yet

Be the first to review this server!