Skip to content

Mark users as "verified" and only send notifications to verified users#1968

Merged
flavorjones merged 5 commits intomainfrom
flavorjones/only-email-confirmed-users
Dec 6, 2025
Merged

Mark users as "verified" and only send notifications to verified users#1968
flavorjones merged 5 commits intomainfrom
flavorjones/only-email-confirmed-users

Conversation

@flavorjones
Copy link
Member

@flavorjones flavorjones commented Dec 5, 2025

Motivation

Today, spammers can add users to an account by consuming join codes; and those users will receive notification emails even if they never log in to confirm their identity. This enables spam delivery via notification emails, which we need to prevent.

Details

Add a verified_at timestamp to users that is set when they reedem a join link, by inserting a verification step into two flows:

  1. if they are already logged in with the identity that matches the email provided during join link redemption:
    • redeem a join link → verify → user join form
  2. otherwise:
    • redeem a join link → submit a valid magic link code → verify → user join form

The "verify" step above is implemented with a new controller, Users::VerificationsController.

The JoinCodesController will redirect the user to new_users_verification_url either directly or via session[:return_to_after_authenticating] and the Sesssions::MagicLinkController. The VerificationsController uses an auto-submit form so that a redirect can be converted into a POST action and write to the database.

Two notes:

  • Account owners are auto-verified on creation.
  • Backfilling "verified" status is nontrivial. A backfill script marks existing users as verified based on evidence of real activity (created cards, comments, reactions, etc.).

Finally, Fizzy will only send notification emails to users who have verified their email address.

Rollout

Ideally this would get rolled out in three steps:

  1. roll out the database migrations and code to set verified_at
  2. run the backfill script
  3. roll out enforcement of verification when emailing notifications

but we could do it all at once if we're careful not to overlap the process with invocation of the recurring job deliver_bundled_notifications.

@flavorjones flavorjones requested a review from jeremy December 5, 2025 19:26
@flavorjones flavorjones force-pushed the flavorjones/only-email-confirmed-users branch from d24779b to 8f6d900 Compare December 5, 2025 19:29
@flavorjones
Copy link
Member Author

@jz the user profile will look like this if a user is unverified (which for a real human should only look like this during the magic link email turnaround time):

screenshot-2025-12-05_12-25-01

@flavorjones flavorjones requested a review from monorkin December 5, 2025 19:40
@flavorjones flavorjones force-pushed the flavorjones/only-email-confirmed-users branch 2 times, most recently from 6adc8a0 to 7a48725 Compare December 5, 2025 22:15
flavorjones and others added 5 commits December 5, 2025 21:51
Reworked the magic link stimulus controller, because the system test
was causing double-submission of the form (because the event was
bubbling up). I think that change simplifies the form and will still
work well for iOS devices.
User are marked as verified after a join code is redeemed. The user is
redirected to Users::VerificationsController, either:

- after submitting a valid magic link code,
- or immediately after redeeming the join code (if they're already
  authenticated with the correct identity)

Account owners are automatically verified when the account is
created (because they have already provided a magic link code at that
point).

This sets up for later commits that will backfill existing users and
require verification before sending notification emails.

Co-Authored-By: Claude <noreply@anthropic.com>
Identifies users who have demonstrated authentication by looking for
evidence of real activity: owners, content creators (cards, comments,
boards, events), action takers (assigners, closers, postponers,
reactors, pinners, filter creators), board accessors, export requesters,
push subscribers, setup completers, and users with active sessions.

Run this script after deploying the verified_at column to backfill
existing users before enabling the notification guard.

Co-Authored-By: Claude <noreply@anthropic.com>
Adds verified? check to bundling_emails? to prevent notification emails
from being sent to users who have never authenticated. This closes the
spam vector where bad actors could create users for known email
addresses and trigger unwanted notifications by mentioning them.

Co-Authored-By: Claude <noreply@anthropic.com>
Display "Unverified" with an explanation instead of the email address
for users who haven't confirmed their identity. Hide the card links
and activity timeline since unverified users won't have any activity.

Co-Authored-By: Claude <noreply@anthropic.com>
@flavorjones flavorjones force-pushed the flavorjones/only-email-confirmed-users branch from 7a48725 to fc8ef33 Compare December 6, 2025 02:51
@flavorjones
Copy link
Member Author

Looks good in staging.

@flavorjones flavorjones merged commit 00db048 into main Dec 6, 2025
11 checks passed
@flavorjones flavorjones deleted the flavorjones/only-email-confirmed-users branch December 6, 2025 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants