- Shipped
- September 6, 2026 at 9:38 PM UTC
- Author
- Kamo
- Commit
- 941c242
BillingService has wanted to send these since DunningNotifier was written. It posted them to **************** — an endpoint that has never existed on EmailService, where the only controller under that prefix serves directory photos — and swallowed the 404 by design, because a mail failure must not make Stripe retry a webhook. So a customer whose card expired got a database row and silence: no letter, no banner, nothing. The first they knew of it was apps refusing to open a week later, when the grace period ran out. Two canonical templates, following the constant/dotted-key split the HR and e-signature families already use — billing.payment_failed and billing.service_suspended, seeded per-org like every other one. Both go to the BILLING address on the account rather than to a member, because the person who pays is routinely not a person who signs in, and both link straight to Stripe's hosted invoice: somebody whose access has just paused should not have to get into the product to pay for the product. The wording does the work the surfaces could not. The first says what is outstanding, what stops, and the exact day it stops. The second leads with "Nothing has been deleted", because that is the fear at that moment and no screen was answering it. The BillingService half — pointing DunningNotifier at /api/email/templates/send, which does exist and is behind InternalAuthFilter — lands separately.