Goshen Email
Email inboxes for AI agents. Each agent gets a real address, threads, attachments, and an API key that reaches only its own mail. People see the same mail in a dashboard, where they can reply, check delivery, and release quarantined messages.
Hosted service · Documentation · API reference · Contributing · License

What it does
- One API call creates an inbox. Inboxes send, receive, and reply, and replies stay threaded with the original conversation.
- The REST API has 17 operations, described in an OpenAPI document. A JSON CLI and an MCP server (hosted or stdio) expose the same 17 operations. There is no SDK; call the REST API from any language.
- An account key reaches every inbox in its account, limited to the scopes you pick. A mailbox key reaches one inbox. No key can release quarantine or create other keys.
- Every send needs an idempotency key. Retrying a timed-out send with the same key and contents never sends a second copy.
- Mail on custom domains arrives through an SMTP gateway that checks SPF, DKIM, and DMARC and scans with Rspamd and ClamAV. Spam, failed authentication, and malware wait in quarantine until a person releases them.
- Signed webhooks report new mail and delivery outcomes: delivered, deferred, bounced, failed, rejected, and complained.
- Optional triage labels new mail with a category, whether it needs a reply, and an urgency score.
How it fits together
flowchart LR
agent["Agent or app"] -->|"API key or MCP"| api["API Worker"]
person["Person"] --> dashboard["Dashboard"]
dashboard --> api
api --> db[("PostgreSQL through Hyperdrive")]
api --> r2[("R2: raw mail and attachments")]
api <-->|"managed domains"| cloudflare["Cloudflare Email Routing and Sending"]
api <-->|"custom domains"| gateway["SMTP gateway: Postfix, Rspamd, ClamAV"]
Try it locally
You need Node.js 24 and pnpm 10.15.1. The local fixture runs the API and the dashboard against an in-memory PostgreSQL database (PGlite). Routing, domain checks, sending, and file storage are test doubles, so you need no accounts or credentials, and no mail leaves your machine.
git clone https://github.com/boringcomputers/goshen-email.git
cd goshen-email
pnpm install --frozen-lockfile
pnpm build
pnpm --filter @bezalel/email exec tsx test/dashboard-fixture.ts
Open http://127.0.0.1:3038/app and sign in with the fixture password,
fixture-dashboard-password-fixture-dashboard-password-. Create an inbox with
the username hello, then deliver a message to it from a second terminal:
curl -X POST http://127.0.0.1:3039/receive \
-H 'Content-Type: application/json' \
-d '{"inboxId":"[email protected]","subject":"Hello from the fixture"}'
The message appears in the inbox. Mail you send or reply with from the
dashboard stays inside the fixture; curl http://127.0.0.1:3039/sends lists it.
The fixture listens on loopback only. Don't deploy it.
Self-host
Goshen Email runs on Cloudflare. A deployment needs:
- A Cloudflare account on the Workers Paid plan, with Email Sending and Email Routing set up for your domain.
- A PostgreSQL database that Cloudflare Hyperdrive can reach. The guide uses PlanetScale.
- An R2 bucket, plus a delivery queue and its dead-letter queue.
- For customer domains at other DNS providers, a server that allows SMTP on port 25.
apps/email/wrangler.jsonc and apps/email-dashboard/wrangler.jsonc contain
the hosted service's account ID, resource IDs, routes, and domains. Replace
them with your own before you deploy. The Worker guide
lists the API Worker's settings. In the dashboard's file:
- Point the
MAIL_APIservice binding at your API Worker's name, and setMAIL_WORKER_URLto that Worker's HTTPS origin. - Set
DASHBOARD_PUBLIC_URLandroutesto your dashboard's domain. - Remove the
NATIVE_MAIL_APIservice binding and theNATIVE_MAIL_*variables. They connect the hosted dashboard to a separate Bezalel deployment.
- Set up the API Worker with the Worker guide. It covers the Cloudflare account, secrets, R2, and queues.
- Create the database and connect Hyperdrive with the
database guide. Put a direct connection string in
apps/email/.envasDATABASE_URLand runpnpm migrate. It applies the full schema and is safe to rerun on upgrades. - Pick a dashboard. The Cloudflare Worker dashboard supports public passwordless accounts; follow the account guide. The Node dashboard below uses one shared owner password and can see every inbox, so use it only for a single operator.
- For customer domains, deploy the SMTP gateway with its runbook.
- Deploy with
pnpm deploy:worker, thenpnpm deploy:dashboard. Deploy the API first, because a newer dashboard calls operations an older API rejects.
Triage needs a TypeSafe API key.
Billing uses Autumn and Stripe; without AUTUMN_SECRET_KEY
the API runs unmetered.
The CI workflow in this repository deploys the hosted service when a commit
lands on main. In a fork, remove the deploy job from
.github/workflows/ci.yml or point it at your own Cloudflare secrets.
Run the Node dashboard
cp apps/email-dashboard/.env.example apps/email-dashboard/.env
openssl rand -hex 32
Save the generated value as DASHBOARD_PASSWORD. Set MAIL_WORKER_URL to your
API Worker's HTTPS origin and MAIL_API_TOKEN to its platform token. Don't
reuse the token as the password.
pnpm build
pnpm start
Open http://127.0.0.1:3031/app. The Worker token stays on the dashboard server. Sessions expire after eight hours, signing out revokes them, and a restart signs everyone out.
To serve it remotely, put it behind an HTTPS reverse proxy that preserves the
Host header, and set DASHBOARD_PUBLIC_URL to the exact public origin. Set
HOST=0.0.0.0 if the proxy runs outside the dashboard's container. List the
proxy's IP addresses in DASHBOARD_TRUSTED_PROXY_IPS and have the proxy
overwrite X-Real-IP with the client's IP, so login throttling applies per
client. Requests from a trusted proxy without a valid X-Real-IP can't sign in.
Use the API
Create a key under API keys in the dashboard, then:
export GOSHENEMAIL_BASE_URL="https://api.goshenemail.com" # or your own API Worker
export GOSHENEMAIL_API_KEY="bze_..."
curl "$GOSHENEMAIL_BASE_URL/v1/inboxes" \
-H "Authorization: Bearer $GOSHENEMAIL_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"username":"research","displayName":"Research agent"}'
The quickstart continues with sending and reading replies. The developer guide covers the CLI and MCP. The CLI and the stdio MCP server aren't on npm yet, so build them from this checkout.
The project started inside Bezalel. The internal workspace packages
(@bezalel/email, @bezalel/email-dashboard, @bezalel/email-gateway) and the
account key prefix bze_ keep names from that time.
Development
Local tests can't prove live delivery, DNS, Cloudflare routing, Hyperdrive, R2, queues, or cron. The SMTP runbook's deployment canary checks those against a real deployment.
Documentation
The public docs at goshenemail.com/docs cover the API from an agent developer's side. The guides here cover running it.
The native-*, verification, and evidence files in docs/ record the
hosted deployment's releases and the original extraction.
Where it came from
Goshen Email began as the email service inside Bezalel, a private Boring Computers project. SOURCE.md records the extracted revision and how fixes move between the two projects.
Contributing and security
Read CONTRIBUTING.md before opening a pull request. Report vulnerabilities privately as described in SECURITY.md, never in a public issue. Everyone taking part follows the code of conduct.
License
Goshen Email uses the Functional Source License 1.1 with an Apache 2.0 future license, FSL-1.1-ALv2. You can use, modify, and self-host it for any purpose, including inside a business. You can't offer it, or anything built from it, in a commercial product or service that competes with Goshen Email. Each release becomes available under Apache 2.0 two years after we publish it.
We published earlier commits under AGPL-3.0-only, and copies taken from them keep that license.