After Cloudflare Agentic Inbox and Mailflare, SaaSMail is the more campaign-shaped email project in this small Cloudflare series.

It is not just a personal inbox.

It is closer to:

shared customer inbox + templates + sequences + suppressions + API

The important framing is that SaaSMail is Cloudflare-native self-hosting. You deploy it into your own Cloudflare account. You do not run a normal Docker Compose stack on a VPS.

What Is SaaSMail?

SaaSMail describes itself as:

The centralized inbox for SaaS teams. One unified timeline per customer - marketing, notifications, and support emails collapsed into a single view, per person.

I inspected:

repo: https://github.com/choyiny/saasmail
commit: 1be56eb77246b3f355469983a970265f63b7c47c
version: 0.10.0
license: Apache-2.0

The app is built for SaaS teams that want to own their customer email workspace without operating a traditional mail server.

That distinction matters.

SaaSMail does not give you IMAP, POP3, Postfix, Dovecot, or a local SMTP stack. It gives you a Cloudflare Worker app with a web UI, API, database, object storage, queue, email routing, and provider integrations.

What You Get

From the current README, docs, and source inspection, SaaSMail includes:

  • one timeline per customer;
  • multiple inboxes with team permissions;
  • thread and chat-style inbox views;
  • per-inbox forwarding;
  • reusable HTML templates with {{variable}} interpolation;
  • multi-step email sequences;
  • delay overrides and sequence step skipping;
  • automatic sequence cancellation when a contact replies;
  • suppressions and unsubscribe management;
  • RFC 8058 one-click unsubscribe;
  • transactional send bypass for account-critical mail;
  • API keys for programmatic sending and inbox access;
  • OpenAPI docs at /doc and /swagger-ui;
  • HMAC-signed message.received webhooks;
  • MCP endpoint for remote AI clients;
  • WebMCP browser tools for page-aware agents;
  • passkey-aware user access and inbox-scoped permissions.

So yes, this is much more marketing/customer-lifecycle aware than Mailflare.

Mailflare feels like a custom-domain webmail workspace. SaaSMail feels like a customer communication layer for SaaS products.

Stack

The current stack is:

Layer SaaSMail uses
Runtime Cloudflare Workers + Hono
Frontend React 19, Vite 7, Tailwind CSS 4, TipTap
Database Cloudflare D1 with Drizzle ORM
Attachments Cloudflare R2
Inbound email Cloudflare Email Routing / Email Workers
Outbound email Cloudflare Email Sending, Resend, Bavimail, or Postmark
Sequences Cloudflare Queues plus hourly cron trigger
Realtime Durable Object NotificationsHub, WebSockets, Web Push
Auth BetterAuth, invite-based users, passkeys
API docs Zod + Hono OpenAPI
AI access MCP and WebMCP

The Worker config makes the Cloudflare dependency explicit:

{
  "main": "worker/src/index.ts",
  "d1_databases": [{ "binding": "DB", "database_name": "saasmail-db" }],
  "r2_buckets": [{ "binding": "R2", "bucket_name": "saasmail-attachments" }],
  "email_routing": { "enabled": true },
  "queues": {
    "producers": [{ "binding": "EMAIL_QUEUE", "queue": "saasmail-sequence-emails" }],
    "consumers": [{ "queue": "saasmail-sequence-emails" }]
  },
  "durable_objects": {
    "bindings": [{ "name": "NOTIFICATIONS_HUB", "class_name": "NotificationsHub" }]
  },
  "triggers": { "crons": ["0 * * * *"] }
}

This is not a static Vite app.

Vite builds the frontend and bundles the Worker deployment, but the production application depends on Worker code, D1, R2, Queues, Durable Objects, Email Routing, and an outbound email provider.

Cloudflare Dependency Map

Cloudflare service Required? What SaaSMail uses it for
Workers Yes Runs the web app, API, email handler, queue consumer, cron job, MCP endpoint, and static assets.
D1 Yes Stores users, inboxes, people, email metadata, templates, sequences, suppressions, OAuth clients, API keys, and settings.
R2 Yes Stores email attachments.
Email Routing Yes for inbound Delivers incoming mail for your domain into the Worker.
Queues Yes for sequences Enqueues scheduled sequence email steps.
Cron Triggers Yes for sequences Checks hourly for due sequence steps.
Durable Objects Yes for realtime Fans out live notifications and push updates per user.
Email Sending Optional outbound path Lets you send without Resend, Bavimail, or Postmark when the EMAIL binding is configured.
Custom domain / DNS Practical requirement Lets the Worker receive and send mail for your domain.

If you want the broader context, I covered the base model in Cloudflare Workers, D1, KV, and R2 - Serverless Apps Without a VPS .

SaaSMail is a more specialized version of that pattern for email.

Email Providers

Inbound is Cloudflare.

Outbound is configurable.

The supported outbound providers are:

  • Cloudflare Email Sending;
  • Resend;
  • Bavimail;
  • Postmark.

SaaSMail chooses the provider at runtime with this precedence:

Bavimail -> Postmark -> Resend -> Cloudflare Email Sending

So if you set more than one provider, the highest one in that order wins.

The relevant secrets are:

BAVIMAIL_API_KEY
BAVIMAIL_ALIAS_ID
POSTMARK_API_KEY
RESEND_API_KEY

Cloudflare Email Sending is configured differently. You uncomment the send_email binding in wrangler.jsonc:

"send_email": [
  { "name": "EMAIL" }
]

Then onboard the domain in Cloudflare Email Service.

Self-Hosting SaaSMail

There is no Home-Lab Docker Compose file for this one.

That is intentional.

The upstream project does not ship a Docker runtime that replaces Cloudflare Workers, D1, R2, Queues, Durable Objects, and Email Routing. A Compose file here would imply a local deployment path that the project does not currently provide.

The manual Cloudflare path is:

git clone https://github.com/choyiny/saasmail.git
cd saasmail
yarn install
wrangler login
wrangler d1 create saasmail-db
wrangler r2 bucket create saasmail-attachments
wrangler queues create saasmail-sequence-emails
cp wrangler.jsonc.example wrangler.jsonc
cp .dev.vars.example .dev.vars

Then edit wrangler.jsonc:

account_id
d1_databases[].database_id
BASE_URL
TRUSTED_ORIGINS
routes / custom domain
optional send_email binding
VAPID_PUBLIC_KEY / VAPID_SUBJECT, if using push

Generate and set the required secrets:

openssl rand -hex 32
wrangler secret put BETTER_AUTH_SECRET
wrangler secret put UNSUBSCRIBE_SECRET

Then add provider secrets if you are not using Cloudflare Email Sending:

wrangler secret put RESEND_API_KEY
wrangler secret put POSTMARK_API_KEY
wrangler secret put BAVIMAIL_API_KEY
wrangler secret put BAVIMAIL_ALIAS_ID

Run the production D1 migrations before first use:

yarn db:migrate:prod

Deploy:

yarn deploy

Finally, configure Cloudflare Email Routing for the domain and add a catch-all rule that routes inbound mail to the SaaSMail Worker.

If Your Domain Is Already on Cloudflare

This setup is much smoother if the domain is already in Cloudflare DNS.

The rough flow is:

  1. Deploy SaaSMail to a Worker.
  2. Bind a custom hostname such as mail.example.com.
  3. Create the D1 database, R2 bucket, Queue, Durable Object migration, cron trigger, and secrets.
  4. Enable Email Routing for the zone.
  5. Route a catch-all or selected addresses to the Worker.
  6. Pick outbound sending: Cloudflare Email Sending, Resend, Bavimail, or Postmark.
  7. Check SPF, DKIM, DMARC, and provider-specific DNS records before sending real customer mail.

Owning the domain in Cloudflare does not remove deliverability work.

It removes a lot of infrastructure work.

Transactional, Marketing, and Cold Outbound

SaaSMail is strongest for consented customer communication.

Use case Fit Notes
Transactional email Good Use POST /api/send and set transactional: true for OTPs, password resets, receipts, and account-critical messages.
Customer lifecycle email Good Templates, sequences, delay controls, suppressions, unsubscribe, and reply cancellation fit onboarding and product follow-up flows.
Marketing newsletters Possible, but check scale It has unsubscribe and sequence primitives, but it is not a full newsletter platform with advanced segmentation, deliverability dashboards, A/B testing, or audience analytics.
Cold outbound Not recommended It is not a cold-email platform. It lacks inbox rotation, warmup, bounce management, reputation isolation, and campaign safety controls.

This is where SaaSMail differs from Mailflare.

In the Mailflare post, I framed Mailflare as poor for marketing newsletters because it is primarily a webmail/workspace with send APIs.

SaaSMail is more credible for SaaS lifecycle email because sequences and suppressions are part of the product.

But that still does not make it a cold outbound tool.

For cold sales outreach, you normally want a specialized platform such as Smartlead, Instantly, Lemlist, or similar, plus separate domains and reputation isolation. Do not run unsolicited prospecting from the same primary domain you use for login links, receipts, support, and customer trust.

API Notes

SaaSMail exposes OpenAPI docs on every running deployment:

/doc
/swagger-ui

API keys use the sk_... format and are sent as:

Authorization: Bearer sk_...

A send request goes to:

POST /api/send

The payload supports attachments through multipart form data and includes fields such as:

{
  "to": "[email protected]",
  "fromAddress": "[email protected]",
  "subject": "Welcome",
  "bodyHtml": "<p>Hello</p>",
  "bodyText": "Hello",
  "transactional": false
}

The transactional field is not cosmetic.

When it is false or omitted, SaaSMail treats the send as marketing-style email: suppressions apply, unsubscribe headers are added, and an unsubscribe footer is appended when needed.

When it is true, SaaSMail bypasses suppressions and skips unsubscribe headers/footer. That is the right behavior for account-critical messages.

Field Note: Local Build Trial

I tried a bounded local build in a temporary clone.

Environment:

node: v18.19.1
npm: 9.2.0
yarn command: /usr/bin/yarn 0.32+git

The documented Yarn command could not be reproduced on this machine because /usr/bin/yarn was not the Node package manager Yarn:

yarn: error: no such option: --frozen-lockfile

Plain npm install also failed on a React type peer dependency conflict.

This worked far enough to install dependencies:

npm install --package-lock=false --legacy-peer-deps

But the build failed on the local Node version:

You are using Node.js 18.19.1.
Vite requires Node.js version 20.19+ or 22.12+.
SyntaxError: The requested module 'node:util' does not provide an export named 'styleText'

My practical note: use Node 20.19+ or Node 22+ for this repo, even though the setup docs still say Node v18+.

I did not attempt a production deployment because that would require creating Cloudflare resources, configuring domain email routing, and choosing a live outbound provider.

SaaSMail vs Mailflare vs Agentic Inbox

These three projects sit near each other, but they solve different problems.

Project Best mental model Deployment
Agentic Inbox Cloudflare AI email demo with MCP and Workers AI Cloudflare-only
Mailflare Custom-domain webmail/workspace with Cloudflare and Docker paths Cloudflare or Docker
SaaSMail Shared SaaS customer inbox plus templates and sequences Cloudflare-only

If you want a Docker homelab mail workspace, Mailflare is the more relevant project.

If you want to study Cloudflare email primitives and AI/MCP integration, Agentic Inbox is the sharper demo.

If you want customer timelines, SaaS lifecycle emails, template sends, suppressions, unsubscribe, and sequence automation, SaaSMail is the more focused tool.

FAQ

Conclusion

SaaSMail is one of the clearer examples of a Cloudflare-native app that is not just a demo.

It uses the platform for real product concerns: inbound mail, outbound mail, relational state, attachments, queues, scheduled work, realtime notifications, webhooks, API access, and AI-agent access.

That also means the deployment boundary is real.

Do not expect a simple Docker Compose file.

Use SaaSMail if you are comfortable with Cloudflare Workers as the runtime and want a self-owned customer email workspace with templates and sequences.

Use Mailflare if you want a broader inbox product with a Docker option.

Use a dedicated outreach tool if your actual goal is cold outbound.