Skip to content

refactor: make member_email_deliveries a typed log - #2832

Merged
mroderick merged 7 commits into
masterfrom
refactor/type-member-email-deliveries
Aug 31, 2026
Merged

refactor: make member_email_deliveries a typed log#2832
mroderick merged 7 commits into
masterfrom
refactor/type-member-email-deliveries

Conversation

@mroderick

@mroderick mroderick commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Problem

member_email_deliveries records which member received an email but not which email it was, and the three-month chaser's "already emailed" exclusion only works because it is the sole writer of the table — any other mailer action that starts logging would silently change chaser behavior.

Change

  • Type each row: new email_type column, populated with the mailer action that sent the email (email_type: action_name in the EmailDelivery concern). The migration backfills existing rows with 'chaser' (all 439 are chasers) and then drops the column default, so every writer must set the type explicitly
  • Integrity in the database: unique index on (member_id, email_type) — a member can only ever have one chaser row, enforced by Postgres rather than by query convention. Built :concurrently in its own migration so the column DDL stays atomic and reversible
  • Scoped exclusion: the chaser now excludes members via email_type: 'chaser' rows only — identical behavior today (it is the only writer), and immune to whatever else gets logged later
  • Replay-tolerant writes: find_or_create_by! so a retried delivery job can't raise on the unique index after the email has already gone out (a create! would fail the job and cause a re-delivery loop); content columns are written on first creation only

No behavior change for anything that exists today: the only logging mailer action is the chaser, and its eligibility outcome is identical under scoped vs blanket exclusion.

Testing

  • Chaser logging spec now asserts the logged row's email_type, and a new spec proves a replayed delivery logs only one row
  • Chaser service spec proves a member whose only logged delivery is of another type remains chaser-eligible, while chaser-typed rows still exclude

Built on #2829 + #2830 (both merged).

@mroderick
mroderick force-pushed the refactor/type-member-email-deliveries branch from d322e0e to 0507893 Compare August 31, 2026 11:09
Rows recorded which member received an email but not which email it
was, and the chaser's "already emailed" exclusion only works because
it is the sole writer of the table.

Type each row with the mailer action that sent it (email_type; 'chaser'
for all existing rows), enforce one row per (member, email_type) with a
unique index, and scope the chaser's exclusion to its own rows so its
behavior is unchanged regardless of what else gets logged. Log writes
are replay-tolerant: a retried delivery job no longer raises after the
email has already gone out.
@mroderick
mroderick force-pushed the refactor/type-member-email-deliveries branch from 0507893 to 89b9986 Compare August 31, 2026 11:21
@mroderick
mroderick marked this pull request as ready for review August 31, 2026 12:00
@mroderick
mroderick requested a review from olleolleolle August 31, 2026 12:00

@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.

Nice!

Comment thread app/mailers/concerns/email_delivery.rb Outdated

it 'logs one row per member even if the delivery is performed twice' do
expect do
described_class.with(member: member).chaser.deliver_now

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.

Suggested change
described_class.with(member: member).chaser.deliver_now
described_class.with(member:).chaser.deliver_now

Comment thread spec/mailers/member_mailer_spec.rb Outdated
Comment thread spec/services/three_month_email_service_spec.rb Outdated
Comment thread spec/services/three_month_email_service_spec.rb Outdated
Comment thread spec/services/three_month_email_service_spec.rb Outdated
Comment thread spec/services/three_month_email_service_spec.rb Outdated
mroderick and others added 5 commits August 31, 2026 14:31
Co-authored-by: Olle Jonsson <olle.jonsson@gmail.com>
Co-authored-by: Olle Jonsson <olle.jonsson@gmail.com>
Co-authored-by: Olle Jonsson <olle.jonsson@gmail.com>
Co-authored-by: Olle Jonsson <olle.jonsson@gmail.com>
Co-authored-by: Olle Jonsson <olle.jonsson@gmail.com>
Co-authored-by: Olle Jonsson <olle.jonsson@gmail.com>
@mroderick

Copy link
Copy Markdown
Collaborator Author

Thank you for your review @olleolleolle. I've created #2833 to enforce the use of the shorthand style, so you don't need to suggest it on every review going forwards.

@mroderick
mroderick enabled auto-merge August 31, 2026 12:56
@mroderick
mroderick merged commit 170a940 into master Aug 31, 2026
9 checks passed
@mroderick
mroderick deleted the refactor/type-member-email-deliveries branch August 31, 2026 12:59
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.

3 participants