Repository navigation
v0.25.0
MillionSend v0.25.0 changes how the regional breaker works: it trips at SES's 0.1% review line and holds only the teams drawing the complaints (#207). On Cloud, several small Free accounts can no longer add up against the shared sending reputation (#205, #206). The content monitor makes fewer false holds (#204), and a few layouts are fixed (#208). There are three database migrations; see "Upgrading a self-hosted instance".
Breaker and deliverability
These apply to self-hosted instances too.
- Trips at SES's review line (#207). A complaint trip now starts at 0.1% (it was 0.4%). It resumes only once the rate is back under 0.08%, so a rate that hovers at the line can't flap. Bounce trips are unchanged.
- Holds the teams drawing the complaints (#207). A complaint trip holds the broadcasts of teams whose own 30-day complaint rate is 0.1% or higher, not the whole region. Manual holds and bounce trips still hold the whole region. The console shows such a region as "paused · N teams", with their names on hover.
- Earlier signal (#207).
- The operator's complaint alert fires from 0.08% (it was 0.3%).
- It names teams by the mail they sent in the last 30 days, which is how SES files complaints.
- A change in SES's account enforcement status mails the operator too.
- Feedback on unknown mail (#207). Some SES complaints and bounces are about mail with no email row: a deleted team's, or a row past retention. These are now kept for 35 days and counted in the operator mails.
- Mailbox simulator (#206). Bounces and complaints from the SES mailbox simulator still suppress the address and fire webhooks. They no longer count toward a team's guardrail rates, the breaker or the Free fuse. A team's own broadcast stats still show them.
The Free plan on Cloud
- One pool per person (#206).
- The Free teams one person creates share one daily cap and one bounce and complaint record.
- Deleting a team doesn't erase the record.
- Free complaints count from the first email.
- Team creation (#206). A Free account may have created at most 3 teams in the last 30 days, deleted ones included.
- Burst allowance (#206). Free mail to providers that report complaints goes out 10 recipients at once, then 10 an hour. That covers Outlook, Yahoo and most other providers; Gmail, Apple and T-Online don't report complaints. Account mail and the onboarding email never wait.
- Review hold and fuse (#207).
- During a complaint trip, Free teams whose mail drew a complaint are held for review.
- Three complaints on Free teams' mail within 7 days trip a platform-wide fuse. It holds Free mail to complaint-reporting providers until an operator releases it from Trust & safety.
Sign-up (#205)
- Turnstile now also guards Google and GitHub sign-in and the product-updates form, wherever Turnstile keys are set. Without keys, nothing changes.
- The product-updates form accepts posts only from its own page, at 20 an hour per instance (it was 200).
- Cloud: one network, an IPv4 address or an IPv6 /64, can open at most 3 accounts a day.
- Setup wizard: the next
@millionsend/setuprelease prints the subscribe link instead of posting the address. On an instance with Turnstile keys, 0.9.2 and earlier are refused and fall back to printing it.
Content monitor (#204)
- Loopback addresses (
localhost,127.0.0.0/8,::1,0.0.0.0) are never treated as a new destination. A verdict that rests on one waits for a person. - A product's billing page on its own app platform is spared like its sign-in page. It is not spared when the mail carries a payment instrument or a Pix code.
- Naming MillionSend as your sending provider, as setup tests do, is no longer read as impersonating it.
- Mail only to the team's own people never waits out the monitor's burst window. It still parks like any other mail.
- The monitor flag opens only on an abuse verdict from the last 7 days, and stays open until the risk falls back under the line.
Interface (#208)
- Buttons keep their label centred whenever a layout widens them. "Accept invitation" on the invite page and an inbox thread's "Newer messages" are as wide as their label again.
- Console team lists (Teams, Trust & safety, limit requests) end a long team name in an ellipsis, with the full name on hover, instead of scrolling sideways.
- Contact properties: the footer counts the in-use properties it lists.
Dependencies and the tracking edge
- Production dependencies (#199):
- smtp-server 3.21.0, nodemailer 10.2.0, better-auth 1.7.12, bedrock-sdk 0.36.0, plus AWS SDK patches.
- A differential run of the SMTP relay, the MX, mail parsing and MIME building found no change in behaviour.
- Tracking edge (#200):
- The edge's Caddy image is pinned by digest (2.11.7).
- The edge serves HTTP/1.1 only until Caddy ships with Go's HTTP/2 fixes.
- Follow the upgrade steps in the tracking edge README on that box.
Upgrading a self-hosted instance
- Upgrade as usual:
docker compose pullanddocker compose up -d, or on Kubernetes upgrade with--version 0.25.0. The MX image changes too (smtp-server 3.21.0), so update it as well. - Three database migrations run on start:
0063addsteams.pool_owner_idand thefree_pool_usagetable.0064adds thefree_pool_burststable and a nullableemails.free_burst_at.0065adds:- the breaker scope, the SES enforcement status and the Free fuse columns;
- a
fusepark reason; - the
ses_unmatched_eventstable; - a partial index on
email_events.
- Plan for the
email_eventsindex. It is built withoutCONCURRENTLY, so writes toemail_events(SES events, opens and clicks) wait while it builds. On a large table, upgrade at a quiet moment. - Cloud-only features. The Free pool, the burst allowance, the Free fuse and the per-network sign-up cap only run on Cloud. On a self-hosted instance their tables stay empty.
- No new environment variables.
- The breaker's new lines apply to self-hosted instances. A complaint pause starts at 0.1% and holds only the teams at or above that line. A self-hosted instance never review-holds anyone.
- Rolling back to v0.24.0 needs no extra step: the migrations only add tables, columns and an enum value that v0.24.0 ignores. A region paused for some teams then reads as paused for every team, until v0.24.0's next breaker run resumes it below 0.4%.