Self-Hosting

Deploy your own mails-agent instance on Cloudflare Workers. Free tier, full control, ~10 minutes.

Prerequisites

RequirementNotes
Cloudflare accountFree plan works. You need Workers, D1, and R2.
Domain nameAdded to Cloudflare DNS (for receiving email via Email Routing).
Resend accountFor sending email. Free tier: 100 emails/day, 3,000/month. Sign up at resend.com.
Node.js 18+For running Wrangler CLI.
Wrangler CLInpm install -g wrangler

Step 1: Clone the repository

git clone https://github.com/Digidai/mails.git
cd mails

Step 2: Create D1 database + R2 bucket

# Create the D1 database
wrangler d1 create mails-agent-db

# Output:
# Created D1 database mails-agent-db
# database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

# Create the R2 bucket (for attachments)
wrangler r2 bucket create mails-agent-attachments

Save the database_id from the output -- you will need it in the next step.

Step 3: Configure wrangler.toml

Open wrangler.toml and fill in your resource IDs:

name = "mails-agent"
main = "src/index.ts"
compatibility_date = "2024-12-01"

[[d1_databases]]
binding = "DB"
database_name = "mails-agent-db"
database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"  # from step 2

[[r2_buckets]]
binding = "ATTACHMENTS"
bucket_name = "mails-agent-attachments"

Step 4: Set secrets

# Resend API key (for sending emails)
wrangler secret put RESEND_API_KEY
# Paste your Resend API key when prompted

# Auth token (clients use this to authenticate)
wrangler secret put AUTH_TOKEN
# Choose a strong random token, e.g.:
# openssl rand -hex 24

# Resend webhook secret (for delivery status tracking)
wrangler secret put RESEND_WEBHOOK_SECRET
# Get this from Resend dashboard → Webhooks → Signing Secret
# Required: without this, delivery status updates are rejected

These secrets are encrypted and stored securely by Cloudflare. They are never visible in your code or dashboard.

Step 5: Run schema migration

# Apply the database schema
wrangler d1 execute mails-agent-db --file=./schema.sql

This creates the tables for mailboxes, emails, threads, webhooks, and other data.

Step 6: Deploy the Worker

wrangler deploy

After deployment, Wrangler prints your Worker URL:

# Output:
# Published mails-agent (1.2s)
# https://mails-agent.YOUR-SUBDOMAIN.workers.dev

Step 7: Configure Cloudflare Email Routing

This is the key step that makes your domain receive email and forward it to the Worker.

  1. Go to the Cloudflare dashboard.
  2. Select your domain.
  3. Navigate to Email > Email Routing > Routes.
  4. Click Catch-all address and set the action to Send to a Worker.
  5. Select your mails-agent Worker.
  6. Save.

Cloudflare automatically configures the required MX records for your domain:

TypeNameValuePriority
MX@route1.mx.cloudflare.net69
MX@route2.mx.cloudflare.net30
MX@route3.mx.cloudflare.net34

If your domain already has MX records (e.g., from Google Workspace), you can use a subdomain instead. For example, set up Email Routing for mail.yourdomain.com and your agents will get addresses like [email protected].

Step 8: Test it

# Check that the Worker is running
curl https://mails-agent.YOUR-SUBDOMAIN.workers.dev/v1/me \
  -H "Authorization: Bearer YOUR_AUTH_TOKEN"

# Expected response:
# {"worker": "mails-worker", "mailbox": "...", "send": true}

# Send a test email
curl -X POST https://mails-agent.YOUR-SUBDOMAIN.workers.dev/v1/send \
  -H "Authorization: Bearer YOUR_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "[email protected]",
    "to": ["[email protected]"],
    "subject": "Hello from self-hosted mails-agent!",
    "text": "If you receive this, everything is working."
  }'

Then send an email to any address at your domain (e.g., [email protected]) and check it arrived:

curl https://mails-agent.YOUR-SUBDOMAIN.workers.dev/v1/inbox \
  -H "Authorization: Bearer YOUR_AUTH_TOKEN"

DNS setup for custom domain (optional)

If you want a clean URL like api.yourdomain.com instead of *.workers.dev:

  1. Go to your Worker's settings in the Cloudflare dashboard.
  2. Under Triggers > Custom Domains, add api.yourdomain.com.
  3. Cloudflare automatically creates the DNS record.

Now your API base URL is https://api.yourdomain.com. Update your CLI config or SDK client accordingly:

# CLI
mails config set worker_url https://api.yourdomain.com

# Python SDK
from mails_agent import MailsClient
client = MailsClient(api_key="...", base_url="https://api.yourdomain.com")

# curl
curl https://api.yourdomain.com/v1/inbox \
  -H "Authorization: Bearer YOUR_AUTH_TOKEN"

Sending email: Resend DNS verification

For Resend to send email from your domain, you need to verify it:

  1. Go to Resend dashboard > Domains.
  2. Add your domain.
  3. Add the DNS records Resend provides (SPF, DKIM, DMARC).
  4. Wait for verification (usually a few minutes).

Once verified, your agent's outbound emails will pass SPF/DKIM checks and land in recipients' inboxes instead of spam.

Upgrading

When a new version of mails-agent is released:

cd mails
git pull
wrangler deploy

If the release includes schema changes, run the migration first:

git pull
wrangler d1 execute mails-agent-db --file=./migrations/XXXX.sql
wrangler deploy

Check the release notes for migration instructions.

Architecture overview

ComponentServicePurpose
WorkerCloudflare WorkersAPI server, email processing
DatabaseCloudflare D1 (SQLite)Emails, mailboxes, threads, webhooks
AttachmentsCloudflare R2Email attachment storage
Email receivingCloudflare Email RoutingMX records, forwards to Worker
Email sendingResendOutbound SMTP via API

Everything runs on Cloudflare's free tier except Resend (free for 100 emails/day, 3,000/month). There are no servers to maintain, no containers to manage, and no cold starts.

Troubleshooting

Emails are not arriving

Sending fails

Auth errors