# mails ships zero runtime dependencies and tells you which provider actually sent

> An email client built for agents, where sending falls through a provider chain and the response says which provider delivered. The published package declares no runtime dependencies at all, and the most useful queries still only exist on the hosted API rather than in the CLI.

**chekusu/mails** — email for agents. Built for AI agents that need to send, receive, and understand emails programmatically

- Repository: https://github.com/chekusu/mails
- Website: https://mails.dev
- Stars: 367 · Forks: 27
- Language: TypeScript
- License: not declared
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/chekusu-mails

## The response tells you which provider took the message

Sending runs through a chain of providers, and that alone is unremarkable. What is unusual is that the result is observable.

The send API response and the outbound rows returned by the inbox both carry a `provider` field, holding either `cloudflare` or `resend`, so you know who actually delivered.

Think about what a fallback chain normally costs you. When a send appears to succeed and nothing arrives, the first question is whether the primary provider accepted it or the fallback did, and the second is whether it was silently swallowed downstream. Without a field naming the provider, that question has no answer except by checking each provider's dashboard separately.

The chain itself has two members. Cloudflare Email Service, which uses a native send binding and is in public beta, and Resend. The default order is Cloudflare then Resend, and it is overridable through an environment variable, so you can force Resend only, or disable either. Naming the tier that handled the request turns a silent fallback into something you can alert on.

## The published package declares no runtime dependencies

The package manifest has an empty dependencies object. Not a short list, not a peer dependency: empty.

The reason given is architectural rather than ascetic. Every provider is reached through a Workers binding or through the platform's native fetch, so no provider SDK is pulled in. Cloudflare's send binding is a platform primitive rather than a library, which removes the usual reason to install an SDK.

What sits in the development dependencies is worth reading rather than assuming is a mistake. There is a MIME parsing library, the TypeScript compiler, and Bun's type definitions. So MIME handling is a real concern the project addresses, and the placement of the library tells you the parsing happens somewhere other than the published runtime path, most plausibly in the Worker, which has its own dependency resolution.

The build targets Bun, and there is a second build mode that compiles a single standalone binary. So this is a package that expects a modern JS runtime rather than trying to cover Node versions it was never tested on.

## The auth token is the mailbox, not a user session

The self-deployed Worker's security model has one rule that is worth stating plainly because it is unusual: the token must match the mailbox.

Every API path requires a bearer token, and that token has to correspond to whichever mailbox is being addressed. If a request names a mailbox in its query, the token must be for that mailbox. If a request reads a specific email, the token must be for the mailbox that email belongs to. If a request sends, the token must be for the from address.

There is no user identity layer and no role hierarchy. A token is a capability over one mailbox, and possessing it is the whole of the authorization decision.

Two consequences follow. A token issued for one mailbox cannot read another, which means there is no cross mailbox exposure by accident. And a leaked token grants exactly one mailbox rather than an account, which keeps the blast radius small.

The health endpoint is always public. And the failure mode for missing configuration is a 503 rather than a 401, which is the correct code: nothing is wrong with your credentials, the server is not configured yet.

## The best queries exist only on the hosted API

This is the gap to know about before you plan around the tool, and the README states it twice.

Attachment filtering, sender filtering, time range filtering, header search, and sender statistics are all available through the hosted HTTP API. They are not available in the CLI or the SDK. The command line and the SDK pass through only three parameters: the query text, the direction, and the limit.

The same restriction applies to the hosted PostgreSQL storage option, which would otherwise be the strongest search in the set. Its weighted full-text search ranks subject highest, then sender, then body, then attachment text, and it adds attachment filtering by type and by name and by presence, sender and time filtering, header queries and sender frequency rankings. All of that is reachable through the hosted API. None of it through the local client.

So the trade is explicit: use the hosted service for real search, or accept three parameters locally. That is a reasonable boundary for a first release, but it means a self-hosted deployment is not feature equivalent to the hosted one, and the README does not pretend otherwise.

## Claiming a mailbox gives you 100 sends a month and no API key

The hosted path is four commands and nothing else to configure:

```bash
mails claim myagent
mails send --to user@example.com --subject "Hello" --body "World"
mails inbox
mails inbox --query "password reset"
```

The first command claims an address at the hosted domain for free. Sending then works without a Resend key, up to 100 free messages per month. Unlimited sending is a configuration change away, setting your own Resend key through the config command.

There is a cap on claiming, at ten mailboxes per person. That is a deliberate abuse control rather than a pricing tier, and it is stated in the CLI reference rather than only in the terms.

The inbox commands cover the operations an agent actually needs: a plain list, full text search ranked by relevance, filtering by direction to look only at inbound or outbound, and a limit. Listing shows a truncated email ID by default, first eight characters, with a flag to see the whole thing.

## The code command prints to stdout so a shell can consume it

The feature that most directly reflects the agent use case is verification code extraction, and the design detail that makes it work in a pipeline is where the output goes.

```bash
mails code --to agent@test.com
mails code --to agent@test.com --timeout 60
```

The extracted code is printed to standard output, so it can be captured into a shell variable directly. That matters because the alternative, parsing a command's formatted output, is exactly the kind of brittle step that breaks when a version changes the wording.

The default wait is thirty seconds, overridable with the timeout flag. On the Worker side the equivalent endpoint long polls for the code, so the wait happens on the server rather than through a client side retry loop.

Extraction covers Chinese, English, Japanese and Korean text, which tells you the author is building for the same non English senders that show up in the platform's other surfaces.

## Storage is auto-detected, and the default is a file in your home directory

Three storage options exist, and the client picks between them by looking at what you have configured rather than asking.

If an API key is configured, storage is remote against the hosted service. If a worker URL is configured, it is remote against your own Worker. Otherwise it falls back to a local SQLite database at a fixed path under the home directory, with no configuration at all.

That fallback is the reason the tool is usable with zero setup, and it is also where the search limitations bite hardest, since the richer ranking described earlier belongs to the hosted PostgreSQL option.

The sync command is what makes the local option more than a cache. It pulls mail from a Worker, hosted or self-deployed, into the local database, accepting a since date or a from scratch flag for a full resync. The stated use is offline access or local backup.

So the design is a local first store with an optional remote authority behind it, which is the sensible arrangement for an agent that may run somewhere without reliable network access and must not lose the record of a verification code it already received.

## The npm update check is rate limited and can be turned off three ways

Small detail, but it is the kind that gets a CLI removed from a pipeline, so it is worth reading.

The client checks npm at most once every 24 hours. When it finds a new version it prints an upgrade reminder, and it goes to standard error rather than standard output, so it does not contaminate a command whose output is being parsed.

There are three ways to disable it. A dedicated environment variable, a notifier suppression variable that other tools also honour, and setting the CI variable, which is the escape hatch that matters in automation.

The published file list explains why there is a skill document in the package at all. Alongside the build output, the manifest ships the skill file and three versions of the readme, in English, Japanese and Chinese.

So the npm package is not only a library and a command line tool, it also carries an agent skill describing how to use them, which is a different distribution channel from documentation on a site.

## Conclusion

Use mails if you are building an agent that needs a working email address without provisioning a mail provider, and if reading verification codes out of an inbox automatically is part of the job. The hosted path needs nothing but a claimed address, and the code command is designed to sit in a shell pipeline. Do not expect the CLI to match the hosted API, because the most useful query capabilities, attachment and sender and time filtering, header search and sender statistics, are documented as not yet wired into the CLI or SDK. Four things to check first. Which sending path you are on, since the hosted service allows 100 sends a month per mailbox and claims are capped at ten per person, while unlimited sending needs your own Resend key. That you configure a mailbox token before anything works, since without one every API path returns a 503. Which storage provider you picked, since the default is a local SQLite file and the alternatives add capability that the CLI cannot reach. And whether you want the fallback chain visible, because the provider field is the only way to tell which side actually accepted your message. Licence is MIT, and the last push to main is dated 6 July 2026.

## FAQ

### What is chekusu/mails?

It is email infrastructure for AI agents, published as a TypeScript CLI and SDK on npm. An agent can claim a hosted mailbox, send and receive mail, search the inbox, extract verification codes automatically, and handle attachments. Receiving runs through a Cloudflare Email Routing Worker you can deploy yourself.

### How do I know which email provider actually sent a message?

The send API response and the outbound inbox rows both carry a provider field holding cloudflare or resend. Sending falls through a chain of Cloudflare Email Service and Resend, defaulting to Cloudflare first, and the order can be overridden or either provider disabled through the EMAIL_PROVIDERS setting.

### Can I search mails by attachment, sender or date from the CLI?

No. Attachment filtering, sender and time filtering, header queries and sender statistics are available through the hosted HTTP API only, and are not yet wired into the CLI or SDK, which pass through just the query text, the direction and the limit.

### How many free emails can I send with a hosted mails.dev mailbox?

Hosted users get 100 free sends per month without providing a Resend key, and can claim up to ten mailboxes per person. For unlimited sending you configure your own Resend API key through the config command.

### How do I authenticate to a self-deployed mails Worker?

With a mailbox level token sent as a bearer credential. The token must match whichever mailbox the request addresses, the email being read, or the from address when sending, so it is a capability over a single mailbox rather than a user session. The health endpoint is always public, and API paths return 503 when no mailbox token is configured.

## Sources

- [chekusu/mails on GitHub](https://github.com/chekusu/mails)
- [Issues](https://github.com/chekusu/mails/issues)
- [Project website](https://mails.dev)
- [README](https://github.com/chekusu/mails/blob/main/README.md)
- [Releases](https://github.com/chekusu/mails/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/chekusu-mails
