A cron job that sends follow-up emails to all bookings where start_time < now AND status = 'confirmed' will send follow-ups to every completed booking that was never explicitly marked as completed — which in many booking systems is the majority. ISO 25010 functional-suitability.functional-correctness requires that no-show follow-ups fire only on verified no-shows, not on a time-based proxy. Without an explicit NO_SHOW status in the booking state machine, operators have no way to distinguish a completed appointment from a no-show, and any automation built on that distinction will misfire. Automated no-show detection without host confirmation also creates legal exposure if follow-up communications are construed as accusations.
Medium because a time-triggered no-show follow-up sends accusatory or unwanted messages to customers who attended their appointment but whose booking was never marked as completed.
Add NO_SHOW to the booking status enum and expose an explicit host-action endpoint that sets it. Trigger the follow-up email from the status-change handler, not from a time-based cron:
// prisma/schema.prisma
enum BookingStatus {
PENDING
CONFIRMED
COMPLETED
NO_SHOW
CANCELLED
}
// src/app/api/bookings/[id]/no-show/route.ts
export async function POST(req: Request, { params }: { params: { id: string } }) {
const booking = await db.bookings.update({
where: { id: params.id },
data: { status: 'NO_SHOW', marked_no_show_at: new Date() },
})
await emailQueue.add('send-no-show-followup', {
bookingId: booking.id,
customerEmail: booking.customer_email,
hostEmail: booking.host_email,
})
return Response.json({ ok: true })
}
Remove any cron job that infers no-show status from appointment time alone.
ID: booking-notifications-reminders.reminders.no-show-follow-up
Severity: medium
What to look for: Find no-show handling in the codebase. Search for: (1) A no_show or no-show status value in the booking status enum or status field. (2) The mechanism that sets this status — it should be a manual action by the host/admin (API endpoint, admin UI button) or a webhook from a check-in system, NOT an automatic timeout. (3) A no-show follow-up email that is ONLY triggered when status = 'no_show' is set, not when the appointment time passes. Trace the email trigger to confirm it is event-driven (status change), not time-driven (cron that checks past appointments).
Pass criteria: Enumerate all booking statuses (at least 4 distinct values expected). ALL of the following: (1) no_show is an explicit, distinct status in the booking model — list all status values and confirm no_show is among them. (2) No-show status is set by a manual action (admin endpoint, host UI) — quote the handler. An automatic timeout that sets no-show does NOT count as pass. (3) Follow-up email is triggered by the status change, not by a time-based check — quote the trigger.
Fail criteria: No-show follow-up sent automatically after appointment time passes (even completed bookings would receive it). No distinct no_show status (system only has confirmed/cancelled). No-show follow-up does not exist. Follow-up triggered by a cron job that checks "appointment time < now AND status = confirmed" — this catches completed-but-not-updated bookings.
Skip (N/A) when: No no-show handling found in the codebase — searched for "no_show", "no-show", "noshow" in status fields, route handlers, and email templates.
Detail on fail: Describe the no-show mechanism found (or "none found"). State how the follow-up is triggered. Example: "No explicit no_show status — status enum at prisma/schema.prisma:15 has only 'pending', 'confirmed', 'cancelled', 'completed'. A cron job at src/jobs/no-show-check.ts:5 runs hourly and sends follow-up emails to all bookings where start_time < now AND status = 'confirmed' — this incorrectly sends follow-ups for appointments that completed but were not marked as 'completed'.".
Remediation: Add an explicit no-show status and trigger follow-up only on status change:
// prisma/schema.prisma
enum BookingStatus {
PENDING
CONFIRMED
COMPLETED
NO_SHOW // Explicit no-show status
CANCELLED
}
// src/app/api/bookings/[id]/no-show/route.ts — host marks no-show
export async function POST(req: Request, { params }: { params: { id: string } }) {
const booking = await db.bookings.update({
where: { id: params.id },
data: { status: 'NO_SHOW', marked_no_show_at: new Date() }
})
// Follow-up triggered by status change, not by time
await emailQueue.add('send-no-show-followup', {
bookingId: booking.id,
customerEmail: booking.customer_email,
hostEmail: booking.host_email,
})
return Response.json({ ok: true })
}
Cross-reference: Booking Flow & Lifecycle audit checks the booking status state machine. Database Audit checks the status enum integrity. Email/SMS Compliance audit checks follow-up email compliance.