feat(reminders): SMS channel for reminder jobs

Tenants paying for the SMS add-on still got email-only reminders —
the channel column existed but nothing armed or dispatched sms jobs.
Booking creation now adds a second job when SMS is enabled and the
customer left a phone; dispatch routes sms jobs through an injected
sender adapter wired to the existing sms.Service, so monthly limits,
usage metering, and Stripe billing apply unchanged. Lifecycle handling
from the previous milestone clears and re-arms both channels together.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-18 13:03:11 +02:00
1 parent c7172544b8
commit 7509e82261
8 files changed
+164 -1

No files matched your search

+2 -1
View File
@@ -108,7 +108,7 @@ func (r *PGRepository) ListDueReminderJobs(ctx context.Context, dueBefore time.T
rows, err := r.pool.Query(ctx, `
SELECT rj.id, rj.tenant_id, t.name, t.locale, t.timezone,
rj.booking_id, rj.channel, rj.scheduled_for,
b.customer_name, b.customer_email, b.reference, b.starts_at, rj.status, b.status
b.customer_name, b.customer_email, b.customer_phone, b.reference, b.starts_at, rj.status, b.status
FROM reminder_jobs rj
INNER JOIN bookings b ON b.id = rj.booking_id
INNER JOIN tenants t ON t.id = rj.tenant_id
@@ -135,6 +135,7 @@ func (r *PGRepository) ListDueReminderJobs(ctx context.Context, dueBefore time.T
&record.ScheduledFor,
&record.CustomerName,
&record.CustomerEmail,
&record.CustomerPhone,
&record.Reference,
&record.StartsAt,
&record.Status,
+5
View File
@@ -418,6 +418,7 @@ type ReminderJobRecord struct {
ScheduledFor time.Time
CustomerName string
CustomerEmail string
CustomerPhone string
Reference string
StartsAt time.Time
Status string
@@ -1386,12 +1387,14 @@ func (r *MemoryRepository) CreateReminderJob(_ context.Context, params ReminderJ
startsAt := time.Now().UTC()
customerName := "Booking Customer"
customerEmail := "[email protected]"
customerPhone := ""
for _, booking := range r.bookings {
if booking.ID == params.BookingID {
reference = booking.Reference
startsAt = booking.StartsAt
customerName = booking.CustomerName
customerEmail = booking.CustomerEmail
customerPhone = booking.CustomerPhone
break
}
}
@@ -1406,6 +1409,7 @@ func (r *MemoryRepository) CreateReminderJob(_ context.Context, params ReminderJ
ScheduledFor: params.ScheduledFor,
CustomerName: customerName,
CustomerEmail: customerEmail,
CustomerPhone: customerPhone,
Reference: reference,
StartsAt: startsAt,
Status: "pending",
@@ -1420,6 +1424,7 @@ func (r *MemoryRepository) ListDueReminderJobs(_ context.Context, dueBefore time
for _, booking := range r.bookings {
if booking.ID == job.BookingID {
job.BookingStatus = booking.Status
job.CustomerPhone = booking.CustomerPhone
break
}
}