Skip to content

feat: signup nudge email sequence for members without a chapter - #2835

Merged
mroderick merged 3 commits into
masterfrom
feat/signup-nudge-email-sequence
Sep 10, 2026
Merged

feat: signup nudge email sequence for members without a chapter#2835
mroderick merged 3 commits into
masterfrom
feat/signup-nudge-email-sequence

Conversation

@mroderick

@mroderick mroderick commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Description

Implements #2384: automated emails to members who signed up via the website but haven't subscribed to a chapter.

A bounded two-stage sequence, recorded in member_email_deliveries (typed log from #2832):

  1. Signup nudge — members created 7–30 days ago with no chapter subscription, not banned
  2. Follow-up — members whose nudge row is at least a month old, still no subscription, not banned

Stage eligibility is derived from the typed log rows; delivery-confirmed writes (via the EmailDelivery concern, email_type: signup_nudge / signup_nudge_followup) mean a lost send retries within the stage-1 window and no member receives the same stage twice — enforced by the (member_id, email_type) unique index. Members who subscribe at any point exit the sequence.

Email copy: both emails currently use the copy from Kimberley's first-email draft (subject "Let's get you connected with codebar!"). Dedicated follow-up copy is pending from Kimberley (issue #2384) and will land as a small follow-up commit.

What happens when (plain language)

Every day, one scheduled job looks at recent sign-ups and sends at most two emails — then never bothers anyone again:

  • A week after someone signs up, they get a short nudge: pick a codebar chapter. Sent exactly once — the send is recorded in a log whose database index makes a second copy impossible, even if the job re-runs.
  • One month after that nudge, if they still haven't subscribed to any chapter, they get one follow-up.
  • After that, silence. Even a member who never subscribes gets no third email, ever.

People who are exempt at every stage:

  • anyone who subscribes to a chapter (they leave the sequence immediately and get the normal chapter welcome email instead),
  • anyone banned from codebar,
  • anyone who signed up more than a month before this ships (the sequence only looks forward — nobody gets a backfilled backlog of old nudges).

Members who signed up but never finished the signup form are included — the nudge is for everyone who created an account and walked away.

Driving it: one daily task (rake chaser:signup_nudges) that must be added to the Heroku Scheduler after merge — see the ops note above.

Ops note ⚠️

After merge, add a Heroku Scheduler entry: rake chaser:signup_nudges, daily — same mechanism as rake chaser:three_months. Without it the feature ships dark. Post-merge verification (first scheduled run creates signup_nudge rows for the current cohort): Morgan.

Steps to verify

  • make test — service spec covers the full stage matrix (window edges, subscribed/banned, both rows = terminal, follow-up anchored to the nudge's send time); mailer specs cover headers, body, and typed logging; rake spec covers the entry point.

@mroderick
mroderick force-pushed the feat/signup-nudge-email-sequence branch 3 times, most recently from 2fcdcde to 3d1ae67 Compare September 1, 2026 10:43
@KimberleyCook

Copy link
Copy Markdown
Contributor

This looks awesome!

Could we tweak the backdate to one month, rather than 2 weeks.

Here is the copy for that first reminder email https://docs.google.com/document/d/1R2DjZiSoBVMnfp0mBS4nz8rMKywlyhhmb9exuDYAUR0/edit?usp=sharing, I'll add to the document today for the second email.

Thaaaank you :)

…apter

Members who sign up but never subscribe to a chapter get at most two
emails, recorded in member_email_deliveries (typed log from #2832):

1. Nudge 7 days after signup to members created in the last 30 days
   who have no subscription and are not banned.
2. One follow-up a month after the nudge if they still have none.

Stage eligibility derives from the typed log rows; delivery-confirmed
writes via the EmailDelivery concern (email_type signup_nudge /
signup_nudge_followup) make a lost send retry within the window while
the (member_id, email_type) unique index prevents duplicates. Members
who subscribe exit the sequence at any point.

Daily entry point: rake chaser:signup_nudges (add to Heroku Scheduler
after merge). Both emails currently use the copy from Kimberley's
first-email draft; dedicated follow-up copy is pending (issue #2384).
@mroderick
mroderick force-pushed the feat/signup-nudge-email-sequence branch from 1637d99 to 0ef2971 Compare September 10, 2026 10:39
@mroderick
mroderick marked this pull request as ready for review September 10, 2026 10:39
@mroderick

Copy link
Copy Markdown
Collaborator Author

@KimberleyCook, the date changes have been applied. I've used the same content for both e-mails for now, so we can ship this today. It's a 5 minute job to replace the content of the second email, once it's ready.

end

def self.never_subscribed
Member.where.not(id: Subscription.select(:member_id))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the Subscription table grows very long, hm, could this be a very long SQL query? Perhaps there are other ways to tell the Member model to exclude all subscribers? A subquery or something.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The query was already a subquery — where.not(id: Subscription.select(:member_id)) generates NOT IN (SELECT member_id FROM subscriptions), an uncorrelated subquery that Postgres runs as a hash anti-join, so it stays linear even as the table grows.

Your comment did surface a real bug though: both member_id columns are nullable in the database, and a single NULL row would make NOT IN exclude every member — the daily job would send nothing. Fixed by filtering NULLs inside the subqueries, with a regression test.

@@ -0,0 +1,13 @@
RSpec.describe 'rake chaser:signup_nudges', type: :task do

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require the rails_helper - at the top of the file.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — added require 'rails_helper' at the top.

@@ -0,0 +1,100 @@
RSpec.describe SignupNudgeEmailService, type: :service do

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Require.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — added require 'rails_helper' at the top.

@olleolleolle olleolleolle left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool!

mroderick and others added 2 commits September 10, 2026 13:40
… specs

Addresses review feedback from #2835:

- never_subscribed and unemailed: both member_id columns are nullable in
  the DB, and a single NULL row would make NOT IN exclude every member,
  silently disabling the daily job. Filter NULLs inside the subquery.
  The query stays an uncorrelated subquery (hash anti-join), which is
  what the reviewer suggested.
- regression test: rows with NULL member_id must not block sends
- require rails_helper at the top of the two new spec files
@mroderick
mroderick enabled auto-merge September 10, 2026 11:48
@mroderick
mroderick merged commit 2ee4e76 into master Sep 10, 2026
9 checks passed
@mroderick
mroderick deleted the feat/signup-nudge-email-sequence branch September 10, 2026 11:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants