MailCheap with Groundmail
MailCheap is an open-source newsletter app (AGPL-3.0) you can host yourself. It can send through Groundmail: every MailCheap account gets its own Groundmail tenant with its own domains, suppression list and pause switch, and Groundmail's webhooks tell MailCheap about deliveries, bounces and complaints.
Newsletter email goes out from Groundmail IPs of its own, apart from transactional and product email, and only for accounts we approve.
Prerequisites
- A Groundmail account approved for newsletter email. Newsletter sending is off until we approve your account: email support@groundmail.dev from your account's address, say you're sending with MailCheap, and tell us who your readers are and how they signed up. Until it's approved, sends answer
403newsletter_not_enabledand MailCheap pauses the campaign with that reason (MailCheap's sign-in codes and alerts still go out, as transactional email). - A self-hosted MailCheap server, set up as its README describes (database, QStash, sign-in), running a version with the Groundmail provider (below).
- A domain to send from, where you can add DNS records. A subdomain such as
news.example.comkeeps your main domain's email apart. - Optional: the Scale or Dedicated plan, for tenants. Without tenants, every MailCheap account sends as your Groundmail account and shares one suppression list.
Get the Groundmail provider
The provider lives on the feat/groundmail-provider branch of Ktryberceo/mailcheap, the branch behind the upstream pull request. It is MailCheap's main plus the provider, so you can deploy it as is, or merge it into your own copy:
# New install
git clone -b feat/groundmail-provider https://github.com/Ktryberceo/mailcheap.git
# Existing MailCheap clone
git remote add groundmail https://github.com/Ktryberceo/mailcheap.git
git fetch groundmail feat/groundmail-provider
git merge groundmail/feat/groundmail-providerThen npm ci and build and deploy as usual. Once the pull request is merged, update from MailCheap's main instead and drop the extra remote.
1. Create an API key
In the Groundmail dashboard, open API Keys and create a full access key. MailCheap creates tenants and domains with it, so a send-only key is not enough.
To try everything before going live, create a test key (gm_test_…) as well: it runs the same checks and fires the same webhooks without delivering anything. See Testing.
2. Add the webhook
Open Webhooks and add an endpoint:
| Field | Value |
|---|---|
| URL | https://<your-mailcheap>/api/webhooks/groundmail |
| Events | email.sent, email.delivered, email.delivery_delayed, email.bounced, email.complained, email.failed, contact.unsubscribed |
| Tenants | Leave empty, so the endpoint gets every tenant's events |
Copy the signing secret (whsec_…): it is shown once. MailCheap checks the Groundmail-Signature header on every event, as described in Webhooks.
3. Set MailCheap's environment variables
| Variable | Value |
|---|---|
EMAIL_PROVIDER |
groundmail |
GROUNDMAIL_API_KEY |
The key from step 1 |
GROUNDMAIL_WEBHOOK_SECRET |
The secret from step 2 |
SENDING_ENABLED |
true once you're ready to send to readers |
SEND_RATE_PER_SECOND |
How many emails a second MailCheap sends, across all accounts |
Redeploy MailCheap. Each account's tenant is created when it starts sending, with the external id mc-<account id>.
Without RESEND_API_KEY, MailCheap's own sign-in codes and alerts go through Groundmail too, as transactional email from EMAIL_FROM. Use an address on a domain or subdomain that no newsletter sends from. The first code adds that domain to your Groundmail account, and MailCheap's server log lists the DNS records it needs.
4. Add your domain in MailCheap
In MailCheap, open Domains and add your sending domain. MailCheap adds it to the account's tenant and lists the records to add:
| Record | Host | Purpose |
|---|---|---|
| TXT | _groundmail.news.example.com |
Domain ownership |
| TXT | <selector>._domainkey.news.example.com |
DKIM signature |
| CNAME | rp.news.example.com → rp.groundmail.dev |
Return path, so SPF passes |
| TXT | news.example.com |
SPF: v=spf1 include:spf.groundmail.dev ~all. If the domain already has an SPF record, add include:spf.groundmail.dev to it instead |
MailCheap also suggests a DMARC record. Once the records are in, click Check now. MailCheap asks Groundmail to verify the domain, and checks again on its own at most once a minute while the domain is being set up.
5. Test with simulator addresses
Groundmail's simulator addresses produce a known outcome and real webhooks, without reaching an inbox or touching your sending reputation.
- In MailCheap, add contacts to your newsletter:
delivered@simulator.groundmail.dev,bounce@simulator.groundmail.dev,block@simulator.groundmail.devandcomplaint@simulator.groundmail.dev. - Send them a campaign.
- On the campaign's report, the first shows as delivered, the hard bounce as bounced (and the contact as bounced), the block bounce as sent (a block is not the address's fault, so it isn't suppressed), and the complaint as complained.
How MailCheap uses Groundmail
| MailCheap | Groundmail |
|---|---|
| Every email for a newsletter: issues, welcome, confirmation and test emails | POST /emails with category: "newsletter" in the account's tenant, with the message id as Idempotency-Key |
| Unsubscribes | Groundmail adds a one-click unsubscribe to newsletter email; a reader who uses it comes back as contact.unsubscribed and is unsubscribed in MailCheap. The links in the email stay MailCheap's |
| Blocking an account | Pauses its tenant. A tenant Groundmail paused blocks the MailCheap account until Groundmail resumes it |
| Daily limits | A new account and each new tenant ramp up. Over the limit, MailCheap waits until it resets at 00:00 UTC and sends the rest |
| Suppressed recipients | A 422 skips the address and keeps it out of the account's next sends |
Good to know
- MailCheap's shared address (
SHARED_DOMAIN,hello@<slug>.<shared domain>) isn't supported with Groundmail yet. Newsletters send from your own domains. - Opens and clicks are tracked by MailCheap itself, so leave open and click tracking off on the domain in Groundmail.