feat(mail): send broadcasts to every active user

Remove the platform_mails opt-out. Broadcasts go to all active users.
V28 drops the column; the PUT /api/users/settings/platform-mails
endpoint and the footer line about turning mails off are gone.
This commit is contained in:
lobo committed 2026-10-02 07:53:35 +02:00
1 parent 0af76da9c7
commit 1b7bafd9f0
25 files changed
+65 -151

No files matched your search

+6 -8
View File
@@ -88,12 +88,11 @@ To Be Played is split into three repositories:
registration was denied", later "your account is blocked" and "your account will be deleted from <date>". The registration sends the browser language (`de` for a German browser, else `en`), which also becomes
the user's app language. Plain text only. See "Mail flow".
- **Broadcast mail**: a PLATFORM_ADMIN writes one mail with subject and text in English and German (plain text),
and sends it to all active users or to picked ones, each in their app language. A user can turn it off
(`user_settings.platform_mails`, default on); the footer of the mail says where. "Send test to me" mails the draft
to the admin only, in the admin's language. One `mail_broadcast` row keeps the texts and the counts (recipients,
sent, failed); `BROADCAST_SEND` goes to `admin_audit`. The sender takes account mails first and stays under
`APP_MAIL_DAILY_LIMIT` per 24 h, 10 of them kept for account mails. A recipient who is blocked, deleted or opted
out before the send counts as failed.
and sends it to all active users or to picked ones, each in their app language. Every active user gets it.
"Send test to me" mails the draft to the admin only, in the admin's language. One `mail_broadcast` row keeps the
texts and the counts (recipients, sent, failed); `BROADCAST_SEND` goes to `admin_audit`. The sender takes account
mails first and stays under `APP_MAIL_DAILY_LIMIT` per 24 h, 10 of them kept for account mails. A recipient who is
blocked or deleted before the send counts as failed.
- **Delete my account** with the password (5 wrong tries per 15 minutes, then `429`). It only sets
`users.deleted_at` and revokes every refresh token: from then on no login, no refresh, no access token, no public
profile, no Steam link, not in the landing stats. Email and display name stay taken; the Steam ID is freed, so an
@@ -139,7 +138,6 @@ To Be Played is split into three repositories:
| `PUT` | `/api/users/settings/profile-style` (`{accent, corners, bannerAppid, backgroundAppid}`; `204`) | access token |
| `PUT` | `/api/users/settings/dashboard-tiles` (`{tiles: [{id, size}]}` in order, at most 20; `{tiles: null}` resets; `204`). Auth checks only the id shape `[A-Z_]{1,30}`: Core and the Frontend skip ids they do not know, and Core never shares the overview or the activity | access token |
| `PUT` | `/api/users/settings/welcome-seen` (`204`; stamps `user_settings.welcome_seen_at`, sent as `settings.welcomeSeenAt` in `/me`, null until then) | access token |
| `PUT` | `/api/users/settings/platform-mails` (`{enabled}`; `204`) | access token |
| `GET` | `/api/admin/broadcasts` (`{results}` with the counts, newest first) | PLATFORM_ADMIN |
| `GET` | `/api/admin/broadcasts/recipient-count?userIds=` (`{count}`; no `userIds` = all) | PLATFORM_ADMIN |
| `POST` | `/api/admin/broadcasts` (`{subjectEn, subjectDe, bodyEn, bodyDe, userIds}`; `201 {recipientCount}`; `409 NO_RECIPIENTS`) | PLATFORM_ADMIN |
@@ -296,7 +294,7 @@ sequenceDiagram
participant A as Auth
participant M as SMTP (Hostinger)
B->>A: admin sends a broadcast
A->>A: mail_broadcast row + one outbox row per active, opted-in user
A->>A: mail_broadcast row + one outbox row per active user
B->>A: register / admin decides / admin blocks / delete account
A->>A: event + mail_outbox row (PENDING), one transaction
loop every minute (MailOutboxService)