Skip to main content
Glama
SDK-E

mailbox-mcp

by SDK-E
README.md
# mailbox

[![CI](https://github.com/SDK-E/mailbox/actions/workflows/ci.yml/badge.svg)](https://github.com/SDK-E/mailbox/actions/workflows/ci.yml)
[![npm](https://img.shields.io/npm/v/@sdk-e/mailbox)](https://www.npmjs.com/package/@sdk-e/mailbox)
[![license](https://img.shields.io/npm/l/@sdk-e/mailbox)](./LICENSE)

A local development **mail sink**: run one SMTP server plus an HTTP API for your whole machine and point every project at it. Emails never leave your device — instead they land in an inspectable inbox with a web UI, a CLI, and an MCP server for AI agents.

One shared instance eliminates per-project port collisions: start it once, connect many.

## Install

```sh
pnpm add -D @sdk-e/mailbox   # or npm i -D / yarn add -D
```

Or run ad hoc:

```sh
npx @sdk-e/mailbox
```

## Quick start

```sh
mailbox            # SMTP on :11025, inbox UI on http://localhost:11090
```

Send mail to `smtp://127.0.0.1:11025` from any app (nodemailer, Resend dev mode, etc.), then:

```sh
open http://localhost:11090   # or: mailbox-cli open
```

## Bins

| Bin           | Purpose                                                                      |
| ------------- | ---------------------------------------------------------------------------- |
| `mailbox`     | Run the sink (SMTP + HTTP + inbox UI)                                        |
| `mailbox-cli` | `list`, `read <id>`, `wait [match] [--timeout s]`, `clear`, `health`, `open` |
| `mailbox-mcp` | MCP server (`list_emails`, `read_email`, `clear_emails`, `wait_for_email`)   |

### CLI examples

```sh
mailbox-cli list
mailbox-cli read 3
mailbox-cli wait "verification code" --timeout 10   # blocks until a new matching message arrives
mailbox-cli clear
```

`wait` only matches messages that arrive **after** it starts — start it before triggering the send.

## Library use

```ts
import { startMailbox } from "@sdk-e/mailbox";

const mailbox = await startMailbox({ smtpPort: 0, httpPort: 0 }); // 0 = ephemeral
console.log(mailbox.smtpPort, mailbox.url);
await mailbox.close();
```

Ideal for integration tests that need an isolated throwaway inbox.

## HTTP API (stable wire protocol)

| Route            | Method | Response                                                       |
| ---------------- | ------ | -------------------------------------------------------------- |
| `/api/health`    | GET    | `{ ok, smtpPort, httpUrl, messageCount }`                      |
| `/api/email`     | GET    | array of `{ id, receivedAt, from, to, subject, text, html }`   |
| `/api/email/:id` | GET    | message above plus `raw` source; `404 {"error": …}` if missing |
| `/api/email/all` | DELETE | `{ ok: true, messageCount: 0 }`                                |
| `/`              | GET    | inbox UI                                                       |

## Configuration

Environment variables (a `.env.local`, then `.env`, in the working directory are loaded if present):

| Variable         | Default           | Purpose                                    |
| ---------------- | ----------------- | ------------------------------------------ |
| `MAIL_SMTP_PORT` | `11025`           | SMTP listen port                           |
| `MAIL_HTTP_PORT` | `11090`           | HTTP API/UI port                           |
| `MAIL_HTTP_URL`  | derived from port | Base URL used by CLI/MCP to reach the sink |
| `MAILBOX_HOST`   | `127.0.0.1`       | Bind interface (loopback by default)       |

Point your app's mailer at `smtp://127.0.0.1:$MAIL_SMTP_PORT` locally; in production keep using your real provider. The server refuses to start when `NODE_ENV=production`.

## Why high ports?

1025/1080 (classic MailHog-style defaults) collide frequently. mailbox defaults to 11025/11090 so several SDK-E projects can share a single instance without stepping on each other or on other local tooling.

## Development

Requires Node >= 20 and pnpm >= 11.

```sh
pnpm install
pnpm verify     # format, lint, typecheck, test, build
```

See [CONTRIBUTING.md](./CONTRIBUTING.md).

## License

[MIT](./LICENSE)