Skip to content

Add email campaign pipeline, recipient resolution, delete endpoint, email send scheduler - #365

Merged
hieunguyent12 merged 5 commits into
devfrom
wchi/email_campaigns_backend
Aug 23, 2026
Merged

Add email campaign pipeline, recipient resolution, delete endpoint, email send scheduler#365
hieunguyent12 merged 5 commits into
devfrom
wchi/email_campaigns_backend

Conversation

@williamchiii

@williamchiii williamchiii commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

This pull request introduces significant improvements to the email campaign system, focusing on enabling scheduled campaign sending, expanding recipient resolution, and enhancing repository and SQL support for these features. The changes span new SQL queries, repository methods, background worker scheduling, and service wiring.

Summary of Most Important Changes:

Scheduled Email Campaigns

  • Added support for scheduled email campaigns, including a background scheduler that periodically enqueues tasks to sweep and send due campaigns. This ensures campaigns set for future delivery are automatically processed when their scheduled time arrives. (apps/api/cmd/email_worker/main.go

Email Campaign Recipient Resolution

  • Introduced new SQL queries and repository methods to resolve recipient groups for campaigns:
    • Applicants by status (e.g., accepted applicants)
    • Users by roles (e.g., admins, staff)
    • Interest subscribers (public signups)
      These methods ensure campaigns reach the correct audience segments. (apps/api/internal/database/queries/email_campaigns.sql

Email Campaign State Management

  • Added atomic "claim for sending" logic to ensure that only one worker can transition a campaign to "sending" at a time, preventing duplicate sends in concurrent environments. (apps/api/internal/database/queries/email_campaigns.sql

  • Added deletion support for email campaigns, scoped by hackathon to prevent cross-event data issues. (apps/api/internal/database/queries/email_campaigns.sql

Application and User Model Enhancements

  • Extended application and user models/queries to include an is_fake flag, allowing filtering of test or fake users/applications from recipient lists. (apps/api/internal/database/sqlc/applications.sql.go

Service Wiring and Handler Updates

  • Updated service and handler wiring to inject the new EmailCampaignService with its dependencies, ensuring campaign logic is available in both the API and worker processes. (apps/api/cmd/email_worker/main.go

These changes collectively enable robust, scheduled, and targeted email campaigns, while laying groundwork for further automation and reliability in campaign delivery.

Resolves applicant recipient groups to deduped contact emails (excluding
is_fake seeded rows), adds raw-HTML task type and SES send methods, registers
all task types in the email worker mux, and adds SendCampaign with
draft/scheduled -> sending -> sent/failed transitions plus an admin-gated
POST /email/campaigns/{campaignId}/send endpoint.
Adds GetUserContactEmailsByRoles (role-based lookup on users, excluding
is_fake rows) and a recipientRoles map, so resolveRecipients now dispatches
applicant groups by application status and role groups by user role.
Only interest_subscribers remains unsupported.
Introduces campaignStore and campaignMailer interfaces in the email package
so the campaign service can be tested with fakes (concrete repository and
email service satisfy them unchanged). Adds 8 tests covering per-recipient
queueing, format routing, cross-group dedup, status transitions including
sending->failed with last_error, and the guard paths.
test: cover SendCampaign send pipeline

Introduces campaignStore and campaignMailer interfaces in the email package
so the campaign service can be tested with fakes (concrete repository and
email service satisfy them unchanged). Adds 8 tests covering per-recipient
queueing, format routing, cross-group dedup, status transitions including
sending->failed with last_error, and the guard paths.
Adds DeleteEmailCampaign query, repo method, and a DeleteCampaign service
method guarded by canDeleteCampaign so draft, scheduled, and failed campaigns
can be removed while sent and sending ones stay on the record. Exposes
DELETE /email/campaigns/{campaignId} with OpenAPI docs.
@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
core Ready Ready Preview Aug 12, 2026 2:43am
xii-front-page Error Error Aug 12, 2026 2:43am

Adds an asynq Scheduler in cmd/email_worker that sweeps every minute for
campaigns whose scheduled_at has passed, failing any more than two hours
late so an outage cannot trigger a surprise blast.

Sends are now claimed with a conditional UPDATE, so overlapping ticks or a
double-clicked Send cannot dispatch the same campaign twice. Also resolves
interest_subscribers, completing all seven recipient types.
@williamchiii williamchiii changed the title Add email campaign pipeline, recipient resolution, and delete endpoint Add email campaign pipeline, recipient resolution, delete endpoint, email send scheduler Aug 12, 2026
@hieunguyent12
hieunguyent12 merged commit 5f5f2b2 into dev Aug 23, 2026
2 of 3 checks passed
williamchiii added a commit that referenced this pull request Aug 30, 2026
Builds the campaign list and create/edit panel against the seven
endpoints shipped in #365. Layout follows the Figma two-pane design:
the list stays visible and shifts left while an inline panel expands
beside it, rather than an overlay.

- Typed hooks for list, get, create, update, send, and delete
- Recipient groups are a union type, so a group the API cannot
  resolve is a compile error rather than a runtime 400
- Format has no default; picking wrong sends raw tags or unrendered
  markup, so it must be a deliberate choice
- Subject and format are not in the Figma but the API requires both

Send, delete, scheduling, and delivery stats are not wired yet.
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