mail-agent is a Codex plugin plus local daemon for email, calendar, and contacts workflows.
The idea is simple: give Codex structured tools for mail instead of making it pretend a human mail client is an API.
Right now the repo supports Fastmail and Google. Fastmail uses native protocol access. Google uses OAuth plus the Gmail, Calendar, and People APIs.
The repo ships three coordinated pieces:
mail-agent: the public CLI and Codex plugin bundle@iomancer/mail-agent-daemon: the local MCP daemon that exposes structured tools@iomancer/mail-agent-shared: shared runtime, config, policy, and secret-store logic
Most people only care about mail-agent. The other two packages exist so the plugin can stay cleanly split between CLI, daemon, and shared runtime code.
v1 currently supports two providers:
- Fastmail
- mail via
JMAP - calendar via
CalDAV - contacts via
CardDAV
- mail via
- Google
- mail via the Gmail API
- calendar via the Google Calendar API
- contacts via the People API
Across those providers, the daemon exposes the same agent-facing tool surface:
list_mailboxessearch_messagesread_message_batchread_threadcompose_messagedraft_replysend_messagearchive_messagesmove_messagestag_messagesmark_messagesdelete_messageswith explicit confirmationlist_calendarsget_eventssearch_contactsget_contact
The tool names are provider-generic on purpose. The adapter layer handles the provider-specific transport and semantics underneath.
Most mailbox integrations for agents land in one of two camps:
- vendor-specific APIs with weak workflow guidance
- general-purpose mail clients that were designed for humans, not models
mail-agent takes a different route:
- structured tool contracts instead of terminal scraping
- skills that teach Codex how to search, shortlist, summarize, draft, and mutate safely
- local-first setup so credentials stay on your machine
- provider-native integrations instead of flattening everything to IMAP first
- an explicit safety model for sends and destructive actions
At runtime the flow is:
- Codex loads the local
Mail Agentplugin bundle. - The plugin starts a local stdio MCP server named
mail-agent. - The daemon reads local account config plus secrets from the OS keychain or a file-backed development store.
- The daemon talks to the configured provider through native APIs or protocols:
- Fastmail:
JMAP,CalDAV,CardDAV - Google: Gmail API, Google Calendar API, People API
- Fastmail:
- Skills tell the agent how to use the tools well, not just that the tools exist.
That last point matters. Tools make actions possible. Skills make the agent behave like it has some judgment.
After install, Codex sees:
- a plugin called
Mail Agent - a local MCP server called
mail-agent - workflow skills:
mail-agentmail-agent-inbox-triagecalendar-briefcontacts-lookup
Example prompts:
- "Use
mail-agentto summarize the latest recruiter thread and draft the reply." - "Use
mail-agent-inbox-triageto sort unread inbox mail into urgent, reply soon, waiting, and FYI." - "Use
calendar-briefto summarize my next two days and flag conflicts." - "Use
contacts-lookupto find Jane from Acme and confirm her best email."
git clone https://github.com/bestlux/mail-agent.git
cd mail-agent
corepack pnpm install
corepack pnpm build
node packages/plugin/dist/bin/mail-agent.js installThen auth whichever account you want first:
node packages/plugin/dist/bin/mail-agent.js auth fastmail --account personal --email you@fastmail.comnode packages/plugin/dist/bin/mail-agent.js auth google --account gmail --email you@gmail.com --client-id <client-id>If browser launch is flaky on your machine:
node packages/plugin/dist/bin/mail-agent.js auth google --account gmail --email you@gmail.com --client-id <client-id> --no-open-browserThen check the runtime:
node packages/plugin/dist/bin/mail-agent.js doctor- Node
22+ pnpmvia Corepack- Codex with plugins enabled
- At least one supported account:
- Fastmail with a
JMAP API tokenplus anapp passwordfor CalDAV/CardDAV - Google with a Google Cloud OAuth desktop client and the Gmail, Calendar, and People APIs enabled
- Fastmail with a
Notes:
- v1 supports Fastmail credentials directly and Google via OAuth loopback auth.
- v1 does not support calendar or contact writes.
delete_messagesis always gated, even in trusted mode.
Clone and build the workspace:
git clone https://github.com/bestlux/mail-agent.git
cd mail-agent
corepack pnpm install
corepack pnpm buildInstall the local plugin bundle into Codex:
node packages/plugin/dist/bin/mail-agent.js installAuth a Fastmail account:
node packages/plugin/dist/bin/mail-agent.js auth fastmail --account personal --email you@fastmail.comAuth a Google account:
node packages/plugin/dist/bin/mail-agent.js auth google --account gmail --email you@gmail.com --client-id <client-id>Run a health check:
node packages/plugin/dist/bin/mail-agent.js doctorThe public package name is mail-agent.
The repo still publishes @iomancer/mail-agent-daemon and @iomancer/mail-agent-shared alongside it because the CLI is split into a public plugin package plus two internal support packages. Most users should ignore that and just install mail-agent.
Once the first npm release is live, the normal install paths will be:
npx mail-agent installor, if you want the CLI on your path:
npm install -g mail-agent
mail-agent install
mail-agent auth fastmail --account personal --email you@fastmail.com
mail-agent auth google --account gmail --email you@gmail.com --client-id <client-id>
mail-agent doctorReleases are meant to go out through GitHub Actions, not from somebody's laptop.
The happy path is:
- Bump the workspace version across the root package plus the three publishable packages.
- Push a tag like
v0.2.0. - Let
.github/workflows/publish.ymlbuild, test, dry-run, and publish the workspace to npm.
The repo is set up for npm trusted publishing from GitHub Actions. For a brand-new package bootstrap, you can still use an NPM_TOKEN repository secret for the first release, then switch to trusted publishers for steady-state releases.
Release details live in RELEASING.md.
You need two credentials:
JMAP API tokenapp passwordfor CalDAV/CardDAV
The interactive auth flow prompts for both. You can also pass them non-interactively:
node packages/plugin/dist/bin/mail-agent.js auth fastmail `
--account personal `
--email you@fastmail.com `
--jmap-token <token> `
--app-password <app-password>Fastmail uses two auth surfaces by design:
- mail uses
JMAPwith the API token - calendar and contacts use
CalDAVandCardDAVwith the app password
Google support uses installed-app OAuth with a local loopback redirect. The flow opens your browser, asks for consent, and stores the resulting refresh token locally so the daemon can refresh access tokens without prompting every run.
Before running auth google, set up Google Cloud:
- Create or choose a Google Cloud project.
- Configure the OAuth consent screen or Google Auth platform branding.
- Set user type to
External. - Keep the app in
Testing. - Add your Gmail account under
Test users. - Enable the Gmail API, Google Calendar API, and People API.
- Create an OAuth client with application type
Desktop app. - Use that client ID when running
mail-agent auth google.
For personal Gmail usage, the expected setup is:
Externaluser typeTestingpublishing status- your own Gmail added under
Test users Desktop appOAuth client
If your Gmail is not listed as a test user, Google will block the app even if the APIs and client are configured correctly.
Example:
node packages/plugin/dist/bin/mail-agent.js auth google `
--account gmail `
--email you@gmail.com `
--client-id <client-id>Optional flags:
--client-secret <secret>if your Google client includes one--full-gmail-accessif you want permanent Gmail delete support--no-open-browserif you want to open the OAuth URL yourself--redirect-host 127.0.0.1--redirect-port 4567
Current default Google scopes:
https://www.googleapis.com/auth/gmail.modifyhttps://www.googleapis.com/auth/calendar.readonlyhttps://www.googleapis.com/auth/contacts.readonly
If you want Gmail delete_messages to permanently delete instead of stopping with a scope error, re-auth with:
node packages/plugin/dist/bin/mail-agent.js auth google `
--account gmail `
--email you@gmail.com `
--client-id <client-id> `
--full-gmail-accessThat swaps the mail scope to https://mail.google.com/, which is broader than the default.
Google Contacts notes:
search_contactsonly searches actual Google Contacts data- it does not search everyone you have ever emailed
- an empty result can just mean the person is not saved in Google Contacts
Useful official references:
- Google OAuth for desktop apps
- Gmail API quickstart for Node.js
- Google Calendar API scopes
- People API contacts guide
Two practical notes:
- sensitive scopes can trigger the "unverified app" screen during testing
- the redirect URI used by the loopback flow must exactly match the configured OAuth client redirect
Runtime state lives under your OS config directory:
- Windows:
%APPDATA%\mail-agent - macOS:
~/Library/Application Support/mail-agent - Linux:
$XDG_CONFIG_HOME/mail-agentor~/.config/mail-agent
Secrets default to the OS keychain via keytar.
For testing or CI you can force file-backed secrets:
$env:MAIL_AGENT_SECRET_BACKEND='file'That stores credentials in the runtime directory instead of the OS keychain. Fine for local development and CI. Not ideal for day-to-day use.
If your package manager blocks native postinstall scripts, keytar may need explicit build approval before the keychain backend works.
Stored secret material depends on the provider:
- Fastmail stores
username,JMAPtoken, and DAV password - Google stores the OAuth access token, refresh token, expiry, scopes, and client metadata needed for refresh
mail-agent is meant to be useful without being reckless.
send_messageis available only after account auth and policy allow it- archive, move, tag, and mark are allowed in trusted mode
delete_messagesis always a two-step flow- calendar and contacts are read-only in v1
Delete is intentionally explicit:
- first call requests a confirmation token
- second call repeats the delete with that token
That keeps permanent deletion out of the "oops, the agent inferred too much" category.
The search tool is where most real workflows start, so the useful details are worth calling out:
collapseThreads: truekeeps broad scans readablemailboxRoleis better than hardcoded mailbox names when possibleexcludeMailingLists: truehelps for person-to-person scanssinceanduntilaccept RFC3339 timestamps orYYYY-MM-DDrefresh: truebypasses short-lived cache entries when polling after send or mutation
Provider notes:
- Fastmail exposes real mailboxes
- Gmail exposes labels plus a pseudo
Archivemailbox role - the tool contract normalizes those differences enough for agents to work reliably, but
list_mailboxesis still worth using before mutations
Those knobs are there because they make real agent workflows noticeably better.
packages/
plugin/ Codex plugin bundle, CLI, installer, skills
daemon/ local MCP daemon and provider adapters
shared/ runtime paths, config, cache, policy, secret handling
.github/ CI and contributor-facing GitHub configuration
Install dependencies:
corepack pnpm installBuild everything:
corepack pnpm buildRun tests:
corepack pnpm testCheck package contents before publishing:
corepack pnpm pack:checkDry-run the workspace publish flow:
corepack pnpm release:dry-runRun the daemon directly:
node packages/plugin/dist/bin/mail-agent.js daemonSupported in v1:
- Fastmail mail via
JMAP - Fastmail calendars via
CalDAVread operations - Fastmail contacts via
CardDAVread operations - Google mail via Gmail API
- Google calendars via Google Calendar API read operations
- Google contacts via People API read operations
Not supported in v1:
- calendar writes
- contact writes
- full local mailbox mirroring
- Microsoft Graph
- generic IMAP/SMTP or generic CalDAV/CardDAV onboarding as first-class flows
- Fastmail mail auth and DAV auth are separate by design
- Google OAuth requires a Google Cloud OAuth desktop client
- Gmail semantics are label-based, so archive and mailbox-like moves are normalized rather than perfectly folder-native
- contact search is a pragmatic address-book scan, not a server-side indexed search engine
- event parsing is intentionally lightweight and does not aim to be a full iCalendar implementation yet
Near-term improvements:
- stronger recurrence and event parsing
- richer contact matching
- Microsoft Graph support
- generic protocol fallback adapters
- optional local indexing for heavier research workflows
- more polished release automation