chekusu/mails: a CLI and SDK that give an agent its own mailbox
email for agents. Built for AI agents that need to send, receive, and understand emails programmatically
At a glance
- What is it?
- mails is a TypeScript tool for sending, receiving and searching email from an agent process, with a hosted mails.dev option and a self-deployed Cloudflare Worker. The design is coherent, but the licence is not stated in the repository metadata and the CLI is narrower than the hosted API.
- Who is it for?
- Adopt mails if an agent needs a real address it can poll for verification codes and search later, and you are willing to run it either on mails.dev or as your own Worker. Do not adopt it if you need the advanced query filters from a script, since the README states those are only exposed through the hosted HTTP API and are not wired into the CLI or SDK, or if you require a stated licence before shipping.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 72 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The gap mails fills: an agent that can hold an address
Most agent tooling treats email as an outbound channel. You wire up an SMTP relay, send a notification, and stop. The harder half is the return path. An agent that signs up for a service needs to receive the confirmation message, find the code inside it, and hand that code back to the calling process. That sequence is what mails is built around. The README describes it as email infrastructure for AI agents, and the CLI reflects that framing: mails claim reserves a name at mails.dev, mails code blocks until a verification message arrives, and the code is printed to stdout so it can be captured in a shell variable. The target user is someone writing an agent that has to complete a signup or password reset flow without a human reading the inbox. The repository topics are agent, cli and mail, which matches the scope. This is not a general mail client and not a transactional email service for marketing blasts.
The provider chain and where mail actually lands
Sending runs through a Worker that holds an ordered list of providers. The README names two: Cloudflare Email Service, which it describes as a native env.EMAIL.send() binding in public beta, and Resend. The default order is cloudflare,resend, and the EMAIL_PROVIDERS secret overrides it, so you can set the value to resend alone or to cloudflare,resend explicitly. Both are optional individually, but at least one must be configured or nothing sends. The response from /api/send and the outbound rows in /api/inbox carry a provider field set to cloudflare or resend, which tells you which one delivered a given message. That field is the useful part of the design, because a fallback chain without per-message attribution leaves you guessing which credential is doing the work. Receiving goes the other direction: an external sender writes to an address, Cloudflare Email Routing hands the message to an email() handler in the Worker, and the Worker parses it. Attachments arrive as MIME parts and are parsed at that point. The README states the project has zero runtime dependencies because every provider call goes through a Workers binding or a plain fetch() rather than a vendor SDK.
Two storage paths: hosted db9.ai or your own D1 and SQLite
The architecture diagram splits storage by deployment mode. On mails.dev the Worker writes to db9.ai, described as cloud PostgreSQL with full-text search and advanced query, and to fs9 for attachment files. Self-deployed, the Worker uses D1 instead. Those are the two server-side stores. There is also a client-side store: mails sync pulls messages from whichever Worker you point at and writes them into a local SQLite file, and the diagram labels that path as offline query and local backup. The sync command takes --since with a date to limit the pull and --from-scratch to re-download everything. So a self-deployed user ends up with D1 as the system of record and SQLite as a local mirror, while a hosted user has db9.ai in the middle. The trade-off is real: the hosted path gives you search features the self-deployed D1 path does not appear to have, based on the diagram, but it also means your mail sits in someone else's PostgreSQL instance.
Getting a mailbox running: claim, send, code
The fastest path needs no credentials. Install with npm install -g mails, or bun install -g mails, or run npx mails without installing. Then mails claim myagent reserves myagent@mails.dev, with a cap of ten addresses per person according to the README. Sending is mails send --to user@example.com --subject "Hello" --body "World", and the README states hosted users get 100 free sends per month. To go beyond that you set your own key with mails config set resend_api_key re_YOUR_KEY. Reading is mails inbox, and searching is mails inbox --query "password reset", which the README says ranks by relevance. Waiting for a code is mails code --to agent@test.com, defaulting to a 30 second timeout and adjustable with --timeout. Because the code goes to stdout, CODE=$(mails code --to agent@test.com) works in a shell script. The SDK exposes the same operations as functions: send, getInbox, searchInbox and waitForCode, imported from the mails package. Attachments send either way, via --attach report.pdf on the CLI or an attachments array with a path in the SDK.
Self-deploying the Worker and the secrets it expects
The self-deployed route starts with cd worker && wrangler deploy. You then need at least one send provider. Option A is wrangler secret put RESEND_API_KEY. Option B is Cloudflare Email Service, which the README marks as public beta, and it requires adding a [[send_email]] block with name = "EMAIL" to wrangler.toml rather than setting a secret. Order is controlled by wrangler secret put EMAIL_PROVIDERS, for example the string resend or cloudflare,resend. Authentication is mailbox-level. A single mailbox uses MAILBOX and AUTH_TOKEN. Multiple mailboxes use AUTH_TOKENS_JSON, a JSON object mapping each address to its own token, which the README shows as {"agent@yourdomain.com":"token1","other@yourdomain.com":"token2"}. On the client, three config keys connect the CLI to your deployment: worker_url, worker_token and mailbox, each set with mails config set. After that, send and inbox route through the Worker, and mails sync pulls the archive down. The beta label on the Cloudflare send path is worth taking literally, since a beta binding can change shape between releases.
Where the CLI stops and the hosted API begins
The clearest limitation is stated in the README itself. Advanced query capability, which it lists as filtering by attachment, sender, time and mail headers, plus sender statistics, is available only through the mails.dev hosted HTTP API. It has not been wired into the mails CLI or the SDK, and the README points to docs/advanced-query-api.md for the details. So a self-deployed user running against D1 has the basic inbox search and nothing more, and even a hosted user cannot reach those filters from a TypeScript call. If your agent needs to ask which senders wrote in the last week, or which messages carried attachments, you are writing HTTP requests against the hosted API by hand. The second limitation is the licence. The repository metadata in the supplied material lists the licence as unknown, and the only licence reference is the npm badge in the README pointing at the LICENSE file. Nothing in the material states which licence that file contains, so a team that needs a known licence before adopting has to open the file. The third is quota: 100 sends per month hosted, and the README does not describe what happens when you exceed it.
How this differs from calling Resend or the Cloudflare binding directly
Resend is the obvious comparison, and it is not a competitor here so much as a component: mails can use a Resend key as its provider. The difference is what sits around the provider. Calling Resend's API directly gives you sending and, on their side, inbound routing if you configure it, but you build the mailbox model, the storage, the search index and the code extraction yourself. mails packages those as one Worker with a mailbox-level token scheme and a CLI that already knows how to poll for a code. The Cloudflare Email Routing binding alone is the other extreme: it gives you the email() handler and the parsed message, and nothing else. No storage, no query, no send fallback. mails is the layer that turns either primitive into something an agent loop can call. The cost of that layer is the extra hop through a Worker you have to deploy and keep configured, and the coupling to Cloudflare's platform for the receive path in both modes.
Upgrades, maintenance and what the release cadence implies
The CLI checks npm at most once every 24 hours and prints an upgrade notice to stderr when a newer version exists. That check is suppressed by MAILS_NO_UPDATE_CHECK=1, and also skipped when NO_UPDATE_NOTIFIER=1 or CI=1 is set, which matters for scripts where a stray stderr line could corrupt parsed output. Recent releases in the supplied material are v1.5.4, v1.5.5 and v1.5.6, dated between mid-April and mid-May 2026, with the last push to main in early July 2026. Three patch releases in roughly a month suggests active maintenance at the patch level, though the material gives no view into what those patches changed. The maintenance cost you take on depends on the mode. Hosted, someone else runs the Worker and db9.ai, and your cost is the quota and the dependency on mails.dev staying up. Self-deployed, you own the Worker, the D1 database, the attachment storage, the provider secrets and the wrangler.toml, and you absorb any breaking change in the Cloudflare Email Service beta binding. mails sync gives you an exit ramp in the self-deployed case, since the local SQLite copy survives a Worker you decide to tear down.
Editorial conclusion
Adopt mails if an agent needs a real address it can poll for verification codes and search later, and you are willing to run it either on mails.dev or as your own Worker. Do not adopt it if you need the advanced query filters from a script, since the README states those are only exposed through the hosted HTTP API and are not wired into the CLI or SDK, or if you require a stated licence before shipping. Verify two things first: the LICENSE file contents, because the npm badge is the only licence reference in the supplied material, and whether the 100-per-month hosted send quota or your own Resend key fits the expected volume.
Community notes