feat(notifications): per-profile email channel with verified addresses
Re-key the email notification channel from login accounts to profiles. Each profile owns its mode, dispatch watermark, and destination address; there is deliberately no fallback to the account email, so the account holder no longer receives mail for every household profile. A profile receives nothing until its own address is verified. - Genericize the watermark-sweep engine over a recipient key (accountChannel[K]): email keys by profile_id, Discord stays on user_id. Delivery reads move into the channel adapters. - Custom addresses verify via single-use SHA-256-hashed token links served by a public endpoint; enabling the channel requires a verified address, and clearing the address switches the channel off. - Addresses are globally unique (case-insensitive): rejected when verified for another profile or matching another account's email or username. Checked at request time, re-checked at verify time (first-to-verify wins), backstopped by a partial unique index. - Every email carries an RFC 8058 one-click unsubscribe link backed by a per-profile capability token, minted lazily under the claim tx. - Child profiles cannot set addresses (and so receive no email in v1). - Verification sends are rate limited (1/min, 10/day per profile); mail.Message gains custom header support for List-Unsubscribe. - Migration drops the account-level prefs table without carrying opt-ins over, so nobody gets surprise emails post-upgrade. Android/Apple notification settings need follow-up for the new profile-scoped response shape and address-management endpoints. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+16
-1
@@ -2352,9 +2352,24 @@ export type NotificationChannelMode =
|
||||
export type NotificationEmailMode = NotificationChannelMode;
|
||||
export type NotificationDiscordMode = NotificationChannelMode;
|
||||
|
||||
/** Account-level (not per-profile): one mode covers all profiles. */
|
||||
/**
|
||||
* Profile-scoped email channel state. Each profile verifies its own
|
||||
* destination address and receives nothing until it has one — there is no
|
||||
* account-email fallback.
|
||||
*/
|
||||
export interface NotificationEmailPreferences {
|
||||
mode: NotificationEmailMode;
|
||||
/** Verified destination; "" = none, channel inert. */
|
||||
custom_email: string;
|
||||
/** Address awaiting link-click verification. */
|
||||
pending_email: string;
|
||||
/** False for child profiles, which cannot set addresses. */
|
||||
can_edit_address: boolean;
|
||||
}
|
||||
|
||||
/** PUT /notifications/email-preferences body: only the mode is writable. */
|
||||
export interface NotificationEmailPreferencesUpdate {
|
||||
mode: NotificationEmailMode;
|
||||
}
|
||||
|
||||
/** Account-level Discord DM channel: link state, mode, and delivery health. */
|
||||
|
||||
Reference in New Issue
Block a user