mailcheap README

The project's README from github.com/marclou/mailcheap. As one markdown file: README.md.

Mailcheap

Mailcheap is a self-hosted newsletter app on Amazon SES. Write an issue, send it to your list, publish it on the web. About $0.10 per 1,000 emails.

Mailcheap overview

Deploy with Vercel

[!IMPORTANT] No support. This is a one-time open-source release. Issues are off, and setup questions go unanswered. Fork it, deploy it, make it yours.

Why

Newsletter platforms charge for every subscriber, every month. With your own SES account you pay for the emails you send: 10,000 readers and 4 issues a month is 40,000 emails, about $4.

Mailcheap is the rest of what a newsletter needs, in one small Next.js app: an editor, a list, signup forms, unsubscribe handling, bounce and complaint handling, stats and a public archive. One admin, no teams, no plans. Your list stays in your database.

Features

Write and send

Grow the list

Keep it healthy

Publish and measure

Screenshots

Editor Campaign report
Editor Campaign report
Contacts Public archive
Contacts Public archive
Issue page

Issue page

All names and addresses in the screenshots are made up.

What you need

Service For Cost
MongoDB Contacts, campaigns, stats Free tier works
Amazon SES Sending About $0.10 per 1,000 emails
Upstash QStash Background sending and scheduling Free tier to start, then pay per message. One message sends 50 emails.
Vercel or any Node.js host Hosting Free tier works
Upstash Redis (optional) Rate limits shared by every server instance Free tier
Vercel Blob (optional) Image uploads in the editor Free tier

Without Redis, Mailcheap counts the same rate limits in each server instance's memory. That still stops a script hammering a form, but on serverless hosts the limits aren't shared between instances and reset on each deploy. Add Redis before you put a signup form on a busy site.

Without Blob, image uploads in the editor are off. HTML and Markdown campaigns can still use images hosted anywhere.

Setup

Plan for about an hour, most of it waiting for DNS and for AWS to approve production access.

1. Database

  1. Create a free cluster on MongoDB Atlas.
  2. Database Access: add a user with a password and the "Read and write to any database" role.
  3. Network Access: allow 0.0.0.0/0. Vercel doesn't have fixed IP addresses.
  4. Connect > Drivers: copy the connection string and add a database name before the ?, like ...mongodb.net/mailcheap?retryWrites=true. That's your MONGODB_URI.

2. Deploy

  1. Click Deploy above.
  2. Fill in MONGODB_URI, ADMIN_EMAIL, ADMIN_PASSWORD (at least 12 characters) and SESSION_SECRET (at least 32; generate it with openssl rand -base64 48). With shorter ones, Mailcheap refuses to start and the logs say which one to fix.
  3. Open https://<your-project>.vercel.app/login and log in.

Sending is off until you finish the next steps, so nothing can go out by mistake. On any other Node.js host: npm ci && npm run build && npm start, with the same variables, behind HTTPS.

If you plan to use your own domain for the dashboard (like mailcheap.example.com), add it in Vercel now and set APP_URL to it. Links in your emails use APP_URL, so it should be final before your first send.

3. Amazon SES

Use one AWS region for everything below (the examples use us-east-1).

  1. Verify a sending domain. In the SES console, go to Identities > Create identity > Domain. Use a subdomain like mail.example.com, so your main domain's email is unaffected. Keep Easy DKIM with RSA 2048 and add the 3 CNAME records SES gives you at your DNS provider.
  2. Set a custom MAIL FROM domain. On the identity, open Custom MAIL FROM domain > Edit, use bounce.mail.example.com, and add the MX and TXT records SES shows. This aligns SPF with your domain.
  3. Add DMARC if your root domain doesn't have one: a TXT record at _dmarc.example.com with v=DMARC1; p=none; to start. Gmail and Yahoo require DMARC from bulk senders.
  4. Request production access. New accounts are in the sandbox (200 emails a day, verified recipients only). In Account dashboard > Request production access, pick Marketing, and explain that readers opt in, that bounces and complaints are suppressed automatically, and that every email has one-click unsubscribe. AWS usually answers within a day.
  5. Create a configuration set, named mailcheap for example, under Configuration sets. Leave SES open and click tracking off: Mailcheap tracks on your own domain.
  6. Create an SNS topic. In the SNS console, create a standard topic, like mailcheap-ses-events, and copy its ARN. Then switch it to signature version 2, which signs messages with SHA256 instead of SHA1: aws sns set-topic-attributes --topic-arn <topic-arn> --attribute-name SignatureVersion --attribute-value 2.
  7. Send SES events to the topic. In the configuration set, open Event destinations > Add destination. Select hard bounces, complaints, deliveries, delivery delays, rejects and rendering failures, then pick Amazon SNS and your topic.
  8. Create an IAM user for Mailcheap with this policy, then create an access key for it:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["ses:SendEmail", "ses:SendRawEmail"],
      "Resource": [
        "arn:aws:ses:us-east-1:<account-id>:identity/mail.example.com",
        "arn:aws:ses:us-east-1:<account-id>:configuration-set/mailcheap"
      ]
    },
    {
      "Effect": "Allow",
      "Action": ["ses:GetAccount", "ses:ListEmailIdentities", "ses:GetEmailIdentity"],
      "Resource": "*"
    }
  ]
}
  1. Add the variables in Vercel and redeploy: SES_REGION, SES_ACCESS_KEY_ID, SES_SECRET_ACCESS_KEY, SES_CONFIGURATION_SET and SES_SNS_TOPIC_ARN.
  2. Subscribe Mailcheap to the topic. In the SNS topic, Create subscription with protocol HTTPS and endpoint https://<your-app>/api/webhooks/ses. Leave raw message delivery off. Mailcheap confirms the subscription by itself; refresh and the status turns to Confirmed.

The Domains page in Mailcheap now shows your domain with its DKIM and MAIL FROM status. Optional: in SES Account dashboard > Suppression list, turn on the account-level list for bounces and complaints too.

4. QStash

  1. In the Upstash console, open QStash and copy QSTASH_TOKEN, QSTASH_CURRENT_SIGNING_KEY and QSTASH_NEXT_SIGNING_KEY (and QSTASH_URL if you picked a region other than the default).
  2. Add them in Vercel and redeploy.

QStash calls APP_URL/api/jobs/... to send each batch, so the app has to be reachable on the internet. Mailcheap checks QStash's signature on every job.

5. Turn sending on

  1. In Mailcheap, create a newsletter. The from address must be on your verified domain, and the postal address shows in every email's footer (anti-spam laws require one).
  2. Set SENDING_ENABLED=true in Vercel and redeploy.
  3. Write a campaign, send yourself a test, then send. For a new domain, set a limit to reach your most engaged readers first, and use Send to more on the report once the first step looks healthy.

Optional

Local development

git clone https://github.com/<you>/mailcheap.git && cd mailcheap
npm install
cp .env.example .env.local   # set MONGODB_URI, ADMIN_EMAIL, ADMIN_PASSWORD, SESSION_SECRET
npm run dev

A local MongoDB works: docker run -d -p 27017:27017 mongo and MONGODB_URI=mongodb://127.0.0.1:27017/mailcheap.

Deliverability basics

Environment variables

Variable Required What it does
MONGODB_URI Yes MongoDB connection string with a database name
ADMIN_EMAIL Yes Dashboard login, and where test emails and alerts go
ADMIN_PASSWORD Yes Dashboard password, 12+ characters. Changing it signs out every session.
SESSION_SECRET Yes Signs sessions and email links. 32+ random characters. Changing it breaks links in sent emails.
APP_URL On other hosts Public URL of the app. On Vercel it defaults to the production domain.
SENDING_ENABLED To send Nothing is sent unless it's true
SES_REGION To send SES region, us-east-1 by default
SES_ACCESS_KEY_ID, SES_SECRET_ACCESS_KEY To send IAM user keys
SES_CONFIGURATION_SET To send Configuration set that publishes events to SNS
SES_SNS_TOPIC_ARN To send SNS topic Mailcheap accepts at /api/webhooks/ses
QSTASH_TOKEN, QSTASH_CURRENT_SIGNING_KEY, QSTASH_NEXT_SIGNING_KEY To send campaigns Background sending and scheduling
QSTASH_URL No QStash region other than the default, or a local QStash dev server
SEND_RATE_PER_SECOND No Emails per second, 1 to 14 (default 10). Use 6 on a free Atlas cluster.
ARCHIVE_HOSTS No Archive domains, like news.example.com=weekly,letters.example.org=monthly
UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN No Shared rate limits
BLOB_READ_WRITE_TOKEN No Image uploads in the editor (set by Vercel Blob)
ALERT_FROM No From address for alert emails, on a verified domain

.env.example has the same list with more detail.

API

Create a key on the API keys page and send it as Authorization: Bearer <key>.

Add a contact: POST /api/v1/contacts

curl -X POST https://<your-app>/api/v1/contacts \
  -H "Authorization: Bearer $MAILCHEAP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"newsletter":"weekly","email":"[email protected]","source":"website","ip":"203.0.113.7"}'

Create a draft: POST /api/v1/campaigns with newsletter, subject, previewText (optional), html or markdown, and archiveSlug (optional). Answers 201 with the draft's id and dashboard URL. Sending stays a click in the dashboard.

List sent issues: GET /api/v1/campaigns?newsletter=<slug> returns each issue's id, subject, preview text, archive URL and send date.

How it works

Support

There's none: issues are off and pull requests may not be reviewed. This is a one-time release, with no updates planned. See SECURITY.md for what's built in.

License

MIT. Made by Marc Lou.

mailcheap (marclou/mailcheap)

Searching Database…

这是什么?Mailcheap 是一个自托管新闻通讯(newsletter)应用,通过你自己的 Amazon SES 账户发送,让你撰写一期内容、发送给订阅列表并发布到网上,成本约为每千封邮件 0.10 美元。
这是托管服务,还是我自己运行的东西?它不是你注册就能用的 SaaS——而是一个需要你自己部署(部署到 Vercel 或任意 Node.js 主机)的小型 Next.js 应用,使用你自己的 MongoDB 数据库和 Amazon SES 账户。
怎么让它跑起来?创建一个免费的 MongoDB Atlas 集群,点击 Deploy to Vercel 并填写 MONGODB_URI、ADMIN_EMAIL、ADMIN_PASSWORD 和 SESSION_SECRET,然后连接 Amazon SES 和 Upstash QStash,再开启 SENDING_ENABLED。在其他 Node.js 主机上则是 npm ci && npm run build && npm start。
除了应用本身还需要哪些服务?用于联系人和统计的 MongoDB、用于发送的 Amazon SES、用于后台发送和定时任务的 Upstash QStash,以及 Vercel 之类的 Node.js 主机;Upstash Redis 和 Vercel Blob 是可选的,用于共享速率限制和图片上传。
出了问题有支持吗?没有。它被描述为一次性的开源发布,issue 已关闭,安装问题也不予解答——你需要自己 fork 并自行运行。
我的订阅者数据存在哪里?存在你自己的 MongoDB 数据库里——Mailcheap 不会让你的订阅列表经由任何第三方新闻通讯平台。
如何防止一次糟糕的发送损害我的发件人信誉?一旦退信率超过 2% 或投诉率超过 0.08%,Mailcheap 会自动暂停活动;它支持确认式订阅(confirmed opt-in),并能自动退订连续忽略多期内容的读者。
一套部署能运行多份新闻通讯吗?可以——每份通讯都有各自的发件地址、回复地址、通信地址和注册表单,同时共享一个管理员登录和一个抑制列表。
它如何处理退订和退信?它发送 RFC 8058 一键退订标头和带签名的退订页面,并通过 SNS 监听 SES 的退信/投诉事件,以保持共享抑制列表的最新状态。
公开归档能与后台管理面板隔离吗?可以,设置 ARCHIVE_HOSTS 后,期刊页面、作者页面和退订链接会放在单独的域名上,使来自编辑器的不可信 HTML 永远不会与后台面板同源。
Creator
marclou
Size
2.5 MB
Stars
1.2K
Updated
CommitOct 17
Age
7d
Commits · 90 Days