5.4 KiB
Code Review: FRE-580 — Email Marketing Sequences
Date: 2026-05-13 Reviewer: Code Reviewer (f274248f-c47e-4f79-98ad-45919d951aa0) Author: Senior Engineer (c99c4ede-feab-4aaa-a9a5-17d81cd80644) Run ID: $PAPERCLIP_RUN_ID
Files Reviewed
| File | Lines | Description |
|---|---|---|
server/services/email-service.ts |
111 | Resend email sender with template rendering |
server/services/email-templates.ts |
418 | HTML/text templates for all sequences |
server/services/email-sequence-service.ts |
527 | Sequence orchestration |
server/trpc/routers/email-marketing.ts |
156 | tRPC API endpoints |
server/trpc/appRouter.ts |
33 | Router registration |
src/db/schema/email_marketing.ts |
132 | Database schema (reviewed for completeness) |
Total: 1,377 lines
P1 Issues
1. Missing Scheduler Integration
Severity: Critical
Location: server/services/email-sequence-service.ts:165
The processDueSteps method is the core scheduling mechanism but is never actually called by any scheduler. The tRPC endpoint processSequence exists but requires manual admin invocation.
Required Fix: Add a cron-based scheduler (e.g., node-cron or @upstash/cron) that calls processDueSteps for each sequence type on an appropriate interval (every 5-15 minutes).
2. Welcome Sequence Enrollment Not Wired
Severity: Critical
Location: server/services/email-sequence-service.ts:124
The welcome sequence has triggerEvent: 'user_signed_up' but there is no registration of a signup event handler that calls enrollUser(userId, 'welcome', email).
Required Fix: Register a signup event listener (or add a hook in the auth registration flow) that calls emailSequenceService.enrollUser(userId, 'welcome', email) after user creation.
3. Email Send Status Tracking Incomplete
Severity: Critical
Location: server/services/email-sequence-service.ts:267-275
status: result.status === 'sent' || result.status === 'id' ? 'sent' : 'pending',
The Resend API returns a message ID (id) on success, not a status field. No webhook handlers are implemented to process delivery events.
Required Fix: Implement Resend webhook handlers (/api/webhooks/resend) to process delivery events (delivered, opened, clicked, bounced, unsubscribed) and update emailSendLog status accordingly.
P2 Issues
4. No Deduplication for Concurrent Scheduler Runs
Severity: High
Location: server/services/email-sequence-service.ts:165-216
If the scheduler runs twice concurrently, the same enrollments could be processed twice.
Required Fix: Add a mutex/lock mechanism or use database-level locking (SELECT FOR UPDATE).
5. tRPC processSequence Allows Any Authenticated User
Severity: High
Location: server/trpc/routers/email-marketing.ts:135-145
Any logged-in user can trigger sequence processing. Should be restricted to admin users.
Required Fix: Add an admin-only middleware check.
6. enrollSequence tRPC Endpoint Accepts Empty Email
Severity: High
Location: server/trpc/routers/email-marketing.ts:102-113
The email parameter is hardcoded to empty string.
Required Fix: Fetch the current user's email from the users table before enrolling.
7. Template Initialization stepNumber Mapping is Fragile
Severity: High
Location: server/services/email-sequence-service.ts:98-110
The uniqueness check uses stepNumber === delayHours, but stepNumber is mapped differently (0→1, 24→2, 72→3). This means the lookup will never find existing templates.
Required Fix: Use the correct stepNumber mapping for the lookup, or add a unique constraint on sequence + stepNumber where stepNumber is the actual ordinal (1, 2, 3).
P3 Issues
8. No Unsubscribe Link Tracking
No tRPC endpoint or API route to handle unsubscribe actions.
9. No Rate Limiting on Email Sending
Could hit Resend API rate limits or trigger spam filters.
10. Analytics Query Uses String Concatenation for SQL
Bypasses drizzle-orm's parameter binding.
11. No Error Handling for Email Service Failures
Failed emails are silently lost.
12. No A/B Testing Implementation Beyond Schema
No logic for traffic splitting, variant selection, or statistical significance.
Architecture Assessment
Positive:
- Template registry pattern is clean and extensible
- Drizzle-ORM schema is well-structured with proper constraints
- tRPC router follows project conventions
- HTML templates use inline styles (email-client compatible)
- Both HTML and text versions provided for all templates
- UTM tracking for analytics is implemented
Areas for Improvement:
- Missing production infrastructure (scheduler, webhooks)
- No error recovery for email delivery failures
- Analytics would be incomplete without webhook integration
Final Disposition
Status: in_progress — Assigned back to Senior Engineer
Priority Fixes Needed:
- Add scheduler cron job for
processDueSteps - Wire welcome sequence enrollment to signup event
- Implement Resend webhook handlers for delivery tracking
Secondary Fixes (P2):
- Add admin-only access to
processSequence - Fix template initialization stepNumber mapping
- Add concurrent execution protection
Review Document: /home/mike/code/FrenoCorp/agents/code-reviewer/reviews/FRE-580-review.md