Skip to content

Add automated reminder for queue items without ticket (#10175)#11189

Merged
nbudin merged 13 commits intomainfrom
feature-10175-queue-without-ticket-reminder
Mar 5, 2026
Merged

Add automated reminder for queue items without ticket (#10175)#11189
nbudin merged 13 commits intomainfrom
feature-10175-queue-without-ticket-reminder

Conversation

@nbudin
Copy link
Contributor

@nbudin nbudin commented Feb 14, 2026

Summary

Implements #10175 - Adds automated email reminders for users who have items in their signup queue but haven't purchased a ticket yet (only for conventions where tickets are required for signup).

Also refactors the reminder system to be more general by combining RemindDraftEventProposalsJob into a new SendRemindersJob that can handle multiple types of reminders.

Changes

Refactored Reminder System

  • Renamed RemindDraftEventProposalsJob to SendRemindersJob
  • Updated RunNotificationsService to use the new job name
  • The new job calls multiple reminder services:
    • RemindDraftEventProposals (existing functionality)
    • RemindQueueWithoutTicket (new functionality)

New Queue Reminder Feature

  • Service: RemindQueueWithoutTicket - finds users who need reminders, using per-convention timing configured via queue_no_ticket_reminder_advance_seconds
  • Notifier: SignupQueue::NoTicketReminderNotifier - handles email delivery
  • Database: Added queue_no_ticket_reminded_at timestamp to user_con_profiles; added queue_no_ticket_reminder_advance_seconds (nullable integer) to conventions
  • Configuration: Added signup_queue/no_ticket_reminder notification type
  • Dynamic Destination: Added SignupRankedChoiceUserConProfileEvaluator for targeting

Convention-Level Configuration

The queue_no_ticket_reminder_advance_seconds column on conventions controls reminder behavior:

  • null (default for existing conventions): reminders are disabled for that convention
  • integer value: reminders are sent this many seconds before the first automated signup round starts

Convention admins can configure this from Convention Settings → Events tab with preset options: Disabled / 1 day before / 3 days before / 1 week before / 2 weeks before.

Reminder Criteria

The service finds and reminds users who:

  • ✅ Have pending items in their signup queue
  • ✅ Don't currently have a ticket
  • ✅ Are in a convention where ticket_mode is required_for_signup or ticket_per_event
  • ✅ Convention has queue_no_ticket_reminder_advance_seconds set (not null)
  • ✅ First automated signup round starts exactly N seconds from now (day-level precision)
  • ✅ Haven't been reminded yet (queue_no_ticket_reminded_at is null)

Email Template

The notification template needs to be configured per-convention in the admin interface at /admin_notifications. The template has access to:

  • user_con_profile - the user's convention profile
  • ticket_name - the convention's word for "ticket"
  • queue_items - array of pending signup ranked choices

Scheduling

Reminders are sent automatically when SendRemindersJob runs, which is triggered daily by RunNotificationsService.

Test Plan

  • Verify the job runs without errors
  • Create a convention with ticket_mode set to required_for_signup and signup_automation_mode set to ranked_choice
  • Set queue_no_ticket_reminder_advance_seconds to "1 week before" in Convention Settings → Events
  • Create a user profile with signup queue items but no ticket
  • Create a signup round starting ~1 week from now
  • Configure the notification template in admin
  • Run SendRemindersJob.perform_now and verify email is sent
  • Verify queue_no_ticket_reminded_at is set on the user profile
  • Run the job again immediately and verify no duplicate email is sent
  • Set the reminder to "Disabled" and verify no reminders are sent
  • Verify badgeless conventions (ticket_mode: disabled) don't trigger reminders

Notes

  • Existing conventions default to null (reminders disabled) — opt-in required
  • No user preference to opt out (can be added later if needed)
  • Email template content must be configured per-convention by admins

🤖 Generated with Claude Code

@nbudin nbudin force-pushed the feature-10175-queue-without-ticket-reminder branch 3 times, most recently from 1b3c19d to e620c1a Compare February 14, 2026 19:37
nbudin and others added 7 commits March 5, 2026 11:18
Refactored RemindDraftEventProposalsJob into a general SendRemindersJob
that sends multiple types of reminders. Added new functionality to remind
users who have items in their signup queue but no ticket (only for
conventions where tickets are required).

Changes:
- Renamed RemindDraftEventProposalsJob to SendRemindersJob
- Created RemindQueueWithoutTicket service to find users needing reminders
- Added queue_no_ticket_reminded_at timestamp to user_con_profiles table
- Added signup_queue/no_ticket_reminder notification type to config
- Created SignupQueue::NoTicketReminderNotifier for email delivery
- Added SignupRankedChoiceUserConProfileEvaluator dynamic destination
- Updated RunNotificationsService to use new job name
- Fixed pre-existing rubocop issue in notifier DSL

The service finds users who:
- Have pending items in their signup queue
- Don't have a ticket
- Are in a convention where tickets are required
- Haven't been reminded in the last week

Reminders are sent automatically when the SendRemindersJob runs (daily
via RunNotificationsService).

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The signup_queue/no_ticket_reminder notifier requires a user_con_profile
parameter, which needs to be handled by the preview factory for
integration tests.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The evaluator now takes a signup_ranked_choice parameter and extracts
the user_con_profile from it, consistent with other evaluators like
SignupUserConProfileEvaluator and OrderUserConProfileEvaluator.

Updated:
- SignupRankedChoiceUserConProfileEvaluator to accept signup_ranked_choice
- SignupQueue::NoTicketReminderNotifier to accept signup_ranked_choice
- RemindQueueWithoutTicket service to pass first pending signup_ranked_choice
- NotifierPreviewFactory to handle signup_ranked_choice parameter

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Instead of finding UserConProfiles and then searching for their
signup_ranked_choices, the service now queries SignupRankedChoices
directly and uses DISTINCT ON to get one per user. This is more
efficient and aligns better with the notifier's interface.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Updated RemindQueueWithoutTicket to:
- Send reminders only once (no repeat reminders)
- Send exactly one week before the first signup round with automated signups
- Only apply to conventions using ranked_choice signup automation
- Use BETWEEN clause to match signup rounds in the reminder window

Updated tests to:
- Create signup rounds with automation_action and ranked_choice_order
- Verify reminder is not sent when already reminded
- Add test for when signup round is not in reminder window
- Update convention to use signup_automation_mode: 'ranked_choice'

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Moved the signup_rounds filtering from .joins to .where clauses, which
provides cleaner native parameter binding with ? placeholders. The join
now only establishes the relationship, while all filtering happens in
.where clauses.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Previously, only tests asserting that emails were sent used
perform_enqueued_jobs. Tests asserting no emails were sent didn't
actually process the job queue, so they could accidentally pass even
if emails would have been sent.

Now all tests consistently use perform_enqueued_jobs to ensure the
job queue is processed and assertions are properly validated.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@nbudin nbudin force-pushed the feature-10175-queue-without-ticket-reminder branch from 306f016 to d299275 Compare March 5, 2026 19:18
nbudin and others added 5 commits March 5, 2026 11:44
- Use many? instead of events.count > 1 (Style/CollectionQuerying)
- Move SCHEDULE_RELEASE_PERMISSIVITY_ORDER before private section
  (Lint/UselessConstantScoping)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a `queue_no_ticket_reminder_advance_seconds` column to conventions
(nullable, defaults to null) that controls how far in advance of the
first automated signup round to send the reminder.  When null, no
reminders are sent for that convention.

The reminder timing is now per-convention via a SQL expression rather
than a hardcoded one-week window.  Convention admins can configure the
setting (disabled / 1 day / 3 days / 1 week / 2 weeks) from the
Convention Settings → Events tab.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The default 10 second timeout is sometimes not enough for Chrome to
start in GitHub Actions, causing flaky system test failures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fixes TypeScript errors caused by missing locale entries for:
- admin.notifications.events.SIGNUP_QUEUE_NO_TICKET_REMINDER
- admin.notifications.destinations.dynamic.SIGNUP_RANKED_CHOICE_USER_CON_PROFILE

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without tsBuildInfoFile set, --noEmit suppresses writing the cache,
making incremental: true a no-op and every type-check a cold start.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nbudin nbudin added enhancement minor Bumps the minor version number on release labels Mar 5, 2026
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

Code Coverage Report: Only Changed Files listed

Package Base Coverage New Coverage Difference
app/graphql/types/convention_input_type.rb 🟢 98.36% 🟢 98.41% 🟢 0.05%
app/graphql/types/convention_type.rb 🟢 79.3% 🟢 79.44% 🟢 0.14%
app/javascript/ConventionAdmin/ConventionFormEventsSection.tsx 🔴 33.33% 🔴 44.44% 🟢 11.11%
app/notifiers/notifier/dynamic_destinations.rb 🟠 56.25% 🟠 56.34% 🟢 0.09%
app/notifiers/notifier_preview_factory.rb 🟢 79.17% 🟢 80% 🟢 0.83%
app/notifiers/signup_queue/no_ticket_reminder_notifier.rb 🔴 0% 🟠 64.29% 🟢 64.29%
app/services/remind_queue_without_ticket.rb 🔴 0% 🟢 100% 🟢 100%
test/services/remind_queue_without_ticket_test.rb 🔴 0% 🟢 100% 🟢 100%
Overall Coverage 🟢 56.51% 🟢 56.67% 🟢 0.16%

Minimum allowed coverage is 0%, this run produced 56.67%

@nbudin nbudin merged commit 755f503 into main Mar 5, 2026
19 checks passed
@nbudin nbudin deleted the feature-10175-queue-without-ticket-reminder branch March 5, 2026 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement minor Bumps the minor version number on release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant