CU-20: Email sequences, reminders, and reconfirmation (time-gated, partial)
Priority: P3 Accounts/sessions: P1 host (ravikantguptaofficial@gmail.com), signed into Calendo via Google; its Gmail is both the host inbox and (via plus-aliases) the invitee inbox. Parallel-safe: Yes — this suite creates its own RUNID-scoped event type, sequence, and bookings; it never edits global availability. Exclusive (rewrites global host availability?): No. Estimated time: 35-45 min for the in-run (immediately verifiable) portion. The reminder/reconfirmation timing portions are time-gated and partly manual — see notes; a full reminder confirmation requires a wait window of up to ~10 min after the cron boundary, and full reconfirmation confirmation requires waiting until the configured pre-meeting window opens. L3 reality checks: Yes — (1) the immediate booking confirmation email and (2) any sequence email whose cron window opens during the run are L3-confirmed in Gmail with template-variable substitution verified. Reminders (24h/1h) and reconfirmation (24/48/72h) are CRON-time-gated; this suite documents the near-term trick and what to look for but marks the exact-timing delivery as time-gated / partly manual.
Goal
This suite proves that Calendo's automated email machinery works end to end against production: a host can attach an automated email sequence (pre-/post-meeting or post-booking) to an event type with template variables; the booking confirmation email is delivered immediately; the cron-driven sequence engine substitutes {{...}} variables correctly and actually delivers the custom email; and reminders (24h/1h) and reconfirmation (24/48/72h before the meeting) are configured and queued correctly. This matters because no-show reduction and meeting prep depend entirely on these emails actually arriving with correct, personalized content — a broken variable or a silent cron failure directly costs the user meetings.
Preconditions
- P1 Calendo session active: Open https://calendo.dev/dashboard/ . You must land on the dashboard (sidebar with
data-tabitems overview, bookings, calendar, analytics, availability, routing-forms, contacts, settings, event-types) without being redirected to a login screen. If you are redirected to /auth/login.html, STOP and FLAG this as a precondition failure per 00-setup-preconditions.md — do NOT attempt a cold email/password or Google password login. - P1 Gmail session active: Open https://mail.google.com in a tab and confirm you are viewing ravikantguptaofficial@gmail.com's inbox without a login prompt. If a login prompt appears, FLAG it; the L3 email checks cannot proceed without it.
- Baseline host availability: P1 must have at least one availability schedule with bookable slots in the next 7 days (so a booking can be created). If the public booking page shows no available days within 6 calendar pages, FLAG as a precondition failure (availability not set up) — do not improvise new availability here (that belongs to the availability suite).
- Resend delivery domain: calendo.dev must be a verified sending domain in Resend (see src/email.js header note). This is infrastructure; if confirmation emails never arrive at all even though the UI says "confirmed," record it as an environment failure, not a test failure, and FLAG it.
- Do not improvise: If any precondition is missing, flag it and stop. Do not create accounts, change global availability, or log in with passwords.
Test data
RUNID convention: at execution start, pick one fresh token RUNID (UTC timestamp, e.g. 20260601-1530). Embed it in every created name/slug/email so reruns never collide and Gmail searches can scope by RUNID.
Create/use exactly these for this run:
- Event type name:
CU20 Email Test <RUNID>(the dashboard auto-derives the slug; read the real slug back from the event card / booking link — do not assume). - Event type slug (read back): likely
cu20-email-test-<RUNID>— but always confirm the actual slug from the public booking link rather than guessing. - Host slug (read back): read from
#bookingLinkValueon the Overview tab (theuser=query param). Do not hardcode. - Sequence (after_booking, near-term): trigger
After booking, delay0minutes, subjectCU20 seq <RUNID> for {{invitee_name}}, body:Hi {{invitee_name}}, thanks for booking {{event_type}} on {{date}} with {{host_name}}. Reply to {{invitee_email}} if anything changes. - Invitee identity (so all mail lands in P1's Gmail): name
Invitee CU20 <RUNID>, emailravikantguptaofficial+inv-<RUNID>@gmail.com. - Optional reminder/reconfirm probe event (time-gated section only): a SECOND event type
CU20 Reconfirm <RUNID>with reconfirmation set, used only if you choose to attempt the time-gated checks.
Steps
Order rationale: the sequence must be created and active BEFORE the booking, because
processEmailSequencesonly fires for bookings created while the sequenceis_active = 1. The booking must be created AFTER the sequence so its creation timestamp falls inside theafter_bookingdelay window on the next cron run.
- Action: Go to https://calendo.dev/dashboard/ . Read the host booking slug: on the Overview tab, read the text of the share-URL element (
#bookingLinkValue); note the value afteruser=as<HOST_SLUG>. Expect: A non-empty URL likehttps://calendo.dev/booking/?user=<HOST_SLUG>.[L1]
- Action: Create the event type. Click the "+ New" button on Overview (
#overviewCreateEtBtn) or "+ New Event Type" on the Event Types tab (#createEtBtn). In the form (#etForm), set Name (#etName) toCU20 Email Test <RUNID>, leave Duration (#etDuration) at 30 min, and set Description (#etDescription) toCU20 sequence + confirmation test <RUNID>. Submit the form (the form's submit button). Expect: A success toast and the new event type appears in the list with nameCU20 Email Test <RUNID>.[L1]
- Action: Read the real slug. Open the event type's public link / copy-link control, or open the Overview booking link, and confirm the generated event slug. Record
<EVENT_SLUG>. Expect: A slug derived from the name (expectedcu20-email-test-<RUNID>). Use the actual observed value going forward.[L2]Capture screenshot:cu20-eventtype-created
- Action: Open Settings. In the sidebar click the Settings tab (
a[data-tab="settings"]). Scroll to the "Email Sequences" card (heading text "Email Sequences", note the helper line "Use {{invitee_name}}, {{event_type}}, {{date}}, {{host_name}} as variables."). Expect: The Email Sequences card is visible with an "+ Add Sequence" button (#createSequenceBtn) and an (initially empty) list (#sequencesList) reading "No email sequences yet."[L1]
- Action: Click "+ Add Sequence" (
#createSequenceBtn). The create form (#sequenceCreateForm) appears. Set Trigger (#seqTrigger) to "After booking" (valueafter_booking). Set Delay minutes (#seqDelay) to0. Set Subject (#seqSubject) toCU20 seq <RUNID> for {{invitee_name}}. Set Body (#seqBody) to the body string from Test data above (the one containing{{invitee_name}},{{event_type}},{{date}},{{host_name}},{{invitee_email}}). Expect: All four fields hold the typed values.[L1]Capture screenshot:cu20-sequence-form-filled
- Action: Click the Create button (
#seqSubmitBtn). Expect: A "Sequence created" toast; the create form hides; the sequence appears in#sequencesListas a row showing trigger "After booking", a delay column (0min), the RUNID subject, and Pause/Delete buttons. (Sequence is active by default — the button reads "Pause", meaningis_active = 1.)[L1]
- Action: Reload the Settings tab (full page reload of https://calendo.dev/dashboard/ then click Settings again). Confirm the sequence still lists with trigger "After booking" and the RUNID subject. Expect: The sequence persists across reload (proves it was saved via POST /api/email-sequences, not just rendered client-side).
[L2]Capture screenshot:cu20-sequence-persisted
- Action: Note the exact UTC clock time NOW (record it; you will use it to bound the Gmail searches and to know which cron run should fire the sequence). The reminder cron runs every 5 minutes (
*/5 * * * *). Expect: A recorded timestamp, e.g.booking_created_at = <UTC HH:MM>.[L1]
- Action: Create the booking as the RUNID invitee. Open the public booking page at
https://calendo.dev/booking/?user=<HOST_SLUG>&event=<EVENT_SLUG>. Wait for the calendar (#booking-viewvisible). Click the first available day (.calendar-day.available); if none on the current month, click the forward nav arrow (.calendar-nav-btn— the last one) and retry (up to 6 months). When a day is selected, click the first time slot (.time-slot). Expect: The confirm form (#confirm-view) becomes visible with name/email inputs.[L1]
- Action: Fill invitee name (
#input-name) =Invitee CU20 <RUNID>and invitee email (#input-email) =ravikantguptaofficial+inv-<RUNID>@gmail.com. Click Confirm (#btn-confirm). Expect: The confirmation screen appears: title (.confirmation-title) reads "Booking Confirmed!", and#confirmation-detailscontains the event nameCU20 Email Test <RUNID>.[L1]Capture screenshot:cu20-booking-confirmed
- Action: On the confirmation screen, inspect the manage block (
#confirmation-manage). Confirm it shows a Cancel link (href contains/booking/cancel.html?token=) and a Reschedule link (href contains/booking/reschedule.html?token=). Copy the cancel link token — you will need it for cleanup. Expect: Both links are present with valid?token=values.[L2]
- Action: Verify the booking persisted in the dashboard. Go to https://calendo.dev/dashboard/ , click the Bookings tab (
a[data-tab="bookings"]). Find the booking forInvitee CU20 <RUNID>on eventCU20 Email Test <RUNID>. Expect: The booking appears with status confirmed, the RUNID invitee name/email, and the chosen date/time.[L2]Capture screenshot:cu20-booking-in-dashboard
- Action (L3, immediate confirmation email): Switch to the Gmail tab (https://mail.google.com). Search
inv-<RUNID>(and if neededsubject:(Confirmed CU20 Email Test <RUNID>)). Open the message whose subject isConfirmed: CU20 Email Test <RUNID> with <P1 host name>. Expect: The confirmation email is present (delivered within ~1-2 min of booking, sent fromCalendo <notifications@calendo.dev>). Body shows Event =CU20 Email Test <RUNID>, the chosen When, Duration 30 minutes, Host = P1's name, and Reschedule/Cancel buttons. This is the immediate, non-cron email.[L3]Capture screenshot:cu20-gmail-confirmation
- Action (L3, sequence email — cron-gated but near-term): The
after_bookingdelay-0 sequence fires on the NEXT*/5cron run after the booking'screated_atenters the ±5-minute window. In practice this is the very next 5-minute cron boundary after step 10. Wait until at least the next 5-minute UTC boundary plus ~2 minutes has passed (worst case ~10 min after booking). Then in Gmail searchsubject:(CU20 seq <RUNID>). Expect: A message arrives with subjectCU20 seq <RUNID> for Invitee CU20 <RUNID>— i.e.{{invitee_name}}was substituted in the SUBJECT. Open it. Verify the body reads, with all variables substituted:Hi Invitee CU20 <RUNID>, thanks for booking CU20 Email Test <RUNID> on <YYYY-MM-DD> with <P1 host name>. Reply to ravikantguptaofficial+inv-<RUNID>@gmail.com if anything changes.Confirm specifically:{{invitee_name}}-> invitee name,{{event_type}}-> event name,{{date}}-> the booking's date (YYYY-MM-DD),{{host_name}}-> P1's name,{{invitee_email}}-> the plus-alias. There must be NO literal{{...}}left anywhere in subject or body.[L3]Capture screenshot:cu20-gmail-sequenceIf the sequence email has not arrived after ~12 minutes: Do NOT immediately fail. Re-search once more, then record this as TIME-GATED / INCONCLUSIVE (cron may have been delayed) and hand it to manual residue — note the exact booking time and search query used so a human can re-check.
- Action (host notification, optional L3): In Gmail search
subject:(New booking: Invitee CU20 <RUNID>). Expect: The host notification emailNew booking: Invitee CU20 <RUNID> - CU20 Email Test <RUNID>is present in the same inbox (host == invitee inbox here), confirming the host-side notification path also fired.[L3]
- Action (reminders — DOCUMENT + configure, time-gated): Reminders are independent of sequences. On the Event Types tab, open the editor for
CU20 Email Test <RUNID>and locate the Reminders block (checkboxes#editEtRemEmail24,#editEtRemEmail1,#editEtRemSms24,#editEtRemSms1). Confirm Email 24h and Email 1h are checked by default. The reminder cron fires for a booking only whennowis within ±5 minutes of exactly 24h (or 1h) beforestart_time(src/worker.jssendPendingReminders); subject will beReminder: CU20 Email Test <RUNID> in 24 hours/... in 1 hours(note the literal "1 hours"). Near-term trick (manual, optional): to actually observe a reminder soon, a host would book a NEW meeting whosestart_timeis ~1h05m in the future, then wait for the next*/5cron after the moment that is exactly 1h before that start — the 1h reminder should then arrive at both invitee and host within ~10 min. This requires an availability slot ~1h out, which production availability may not offer, so mark it as time-gated. Expect (in-run): The reminder checkboxes are present and default-on; you confirm configuration, NOT delivery, in the normal run.[L1]for configuration; reminder DELIVERY is[L3]but TIME-GATED / partly manual. Capture screenshot:cu20-reminder-settings
- Action (reconfirmation — DOCUMENT + configure, time-gated): Still in the event editor, locate the Reconfirmation dropdown (
#editEtReconfirmation) with options None / 24h before / 48h before / 72h before. Set it to24h before(value24) and save the event type. Reconfirmation fires viasendPendingReconfirmationswhenstart_time - 24h <= now < start_timeandreconfirmation_sent_at IS NULL; subjectPlease confirm: CU20 Email Test <RUNID> with <P1 host name>, with green "Confirm Attendance" (link/api/bookings/:id/reconfirm?token=) and red "Cancel Meeting" buttons. Clicking Confirm marksreconfirmed = 1. Near-term trick (manual, optional): to observe a reconfirmation email soon, a host would book a meeting starting <24h out on an event type withreconfirmation_hours = 24; the next cron run would queue the reconfirmation email. Whether a <24h slot exists depends on availability + min-notice, so mark it as time-gated. Expect (in-run): The dropdown saves24h beforeand the event type reload shows the value retained (#editEtReconfirmation=24).[L1]/[L2]for configuration; reconfirmation DELIVERY is[L3]but TIME-GATED / partly manual. Capture screenshot:cu20-reconfirmation-setting
- Action: Re-open the event editor after a reload and confirm both Reminders and Reconfirmation settings persisted (
#editEtReconfirmationstill24; reminder checkboxes still in their saved state). Expect: Settings survive reload (proves PUT /api/event-types/:id savedreconfirmation_hoursandreminder_settings).[L2]
L3 reality checks
Perform these in the browser, in Gmail (https://mail.google.com) as ravikantguptaofficial@gmail.com:
- Immediate confirmation email (required): Search
inv-<RUNID>. Confirm a message with subject exactlyConfirmed: CU20 Email Test <RUNID> with <P1 host name>fromnotifications@calendo.dev. Open it and confirm Event, When, Duration (30 minutes), Host, and the Reschedule + Cancel buttons render. This is non-cron and must arrive within ~2 min. - Sequence email with variable substitution (required if it arrives in-window): Search
subject:(CU20 seq <RUNID>). Confirm subjectCU20 seq <RUNID> for Invitee CU20 <RUNID>(proves{{invitee_name}}substitution in subject). Open and confirm the body has every variable substituted (invitee name, event type, date, host name, invitee email) and contains NO literal{{...}}. If absent after ~12 min, mark TIME-GATED / INCONCLUSIVE and hand to manual residue with the exact timestamp + query. - Host notification (optional): Search
subject:(New booking: Invitee CU20 <RUNID>); confirm it landed. - Reminders (TIME-GATED — partly manual): A 24h/1h reminder email (
Reminder: CU20 Email Test <RUNID> in 24 hours/in 1 hours) only arrives when the cron run coincides with the 24h/1h-before window. Do NOT expect it during a single short run. Hand the exact-timing delivery check to the human: book a meeting ~1h05m out (if a slot exists) and re-check Gmail after the next cron past the 1h-before mark. - Reconfirmation (TIME-GATED — partly manual): A reconfirmation email (
Please confirm: CU20 Email Test <RUNID> with <P1 host name>) only arrives oncenowreaches 24h before a booking's start on an event type withreconfirmation_hours = 24. Hand the exact-timing delivery + the "Confirm Attendance" link click (which should flipreconfirmedto 1 and show a confirmation message) to the human. - Calendar: This suite does not assert calendar-event creation (that is covered by the Google/Outlook calendar suites). No Google Calendar / Outlook event check is required here.
Cleanup
Leave both accounts clean for the next run:
- Cancel the booking: Open the cancel link captured in step 11 (
https://calendo.dev/booking/cancel.html?token=<TOKEN>), or in the dashboard Bookings tab open theInvitee CU20 <RUNID>booking and cancel it. Confirm status flips to cancelled. (This also generates a cancellation email to the same RUNID inbox — expected; it is searchable assubject:(Cancelled: CU20 Email Test <RUNID>).) - Delete the email sequence: Settings tab -> Email Sequences card -> find the
CU20 seq <RUNID>row in#sequencesList-> click Delete (the row'sdeleteSequence(...)button) -> confirm. Reload and verify it is gone. - Delete the event type(s): Event Types tab -> delete
CU20 Email Test <RUNID>(andCU20 Reconfirm <RUNID>if you created it). Confirm removed from the list. (Note: if the booking was not cancelled first, deletion may be blocked or orphan the booking — cancel the booking before deleting the event type.) - Gmail housekeeping (optional but preferred): In Gmail, select the RUNID messages (search
inv-<RUNID>andsubject:(CU20 seq <RUNID>)andsubject:(CU20 Email Test <RUNID>)) and archive or trash them so the host inbox stays uncluttered for the next run. - Verify clean state: Re-load the dashboard; the RUNID event type, sequence, and booking must all be absent.
Pass/Fail criteria
The run PASSES only if ALL of these hold:
- An email sequence with template variables was created via the UI, appeared in the list, and persisted across reload.
[L1][L2] - A booking was created for the RUNID invitee and shows as confirmed in both the public confirmation screen and the dashboard Bookings tab.
[L1][L2] - The immediate confirmation email arrived in Gmail with subject
Confirmed: CU20 Email Test <RUNID> with <host>and correct details.[L3] - IF the sequence cron window opened during the run: the sequence email arrived with subject
CU20 seq <RUNID> for Invitee CU20 <RUNID>and a fully variable-substituted body containing NO literal{{...}}. (If it did not arrive within ~12 min, this single item is recorded TIME-GATED / INCONCLUSIVE and does NOT fail the run, but MUST be handed to manual residue.) - Reminder checkboxes and the Reconfirmation dropdown were configured, saved, and persisted across reload.
[L1][L2] - Cleanup completed: booking cancelled, sequence deleted, event type(s) deleted, RUNID state absent from the dashboard.
The run FAILS if: the sequence cannot be saved/persisted; the booking cannot be created; the immediate confirmation email never arrives (after confirming it is not a verified-domain/environment issue — if it is, FLAG as environment failure, not test failure); a delivered sequence email contains an UNsubstituted {{...}} or a wrong substitution; or any saved reminder/reconfirmation setting fails to persist.
Evidence to capture
cu20-eventtype-created,cu20-sequence-form-filled,cu20-sequence-persistedcu20-booking-confirmed,cu20-booking-in-dashboardcu20-gmail-confirmation,cu20-gmail-sequence(with subject + body visible so variable substitution is auditable)cu20-reminder-settings,cu20-reconfirmation-setting- Notes: the RUNID used; the recorded booking
created_atUTC time; the exact Gmail search queries run; for any time-gated item, whether it was OBSERVED, TIME-GATED/INCONCLUSIVE, or DEFERRED to human.
Manual residue / cannot-verify
- Reminder delivery timing (24h/1h): Cannot be confirmed inside a normal short run unless a booking happens to sit exactly 24h or 1h out at cron time. Human must confirm an actual
Reminder: ... in 24 hours/in 1 hoursemail lands at the configured window. (Note the known cosmetic quirk: the subject says "in 1 hours", not "in 1 hour".) - Reconfirmation delivery + confirm-click effect: Human must confirm the
Please confirm: ...email lands oncenowcrosses the configured pre-meeting window, and that clicking "Confirm Attendance" returns a success message and flipsreconfirmedto 1 (and that "Cancel Meeting" cancels). Not observable in a single short run. - Sequence email if cron is delayed: If the
after_bookingdelay-0 email did not arrive in ~12 min, a human should re-check Gmail (subject:(CU20 seq <RUNID>)) later; cron lag, not a code bug, is the likely cause. before_meeting/after_meetingtriggers: This suite only exercisesafter_booking(the only near-term-observable trigger). The other two trigger types are not delivery-verified in-run.- SMS reminders: The SMS reminder paths (
sms_24h/sms_1h,#editEtRemSms24/#editEtRemSms1) are out of scope here (require Twilio config + a real phone number) — human/separate suite. - Resend dashboard / delivery logs: Whether Resend actually accepted/delivered (vs. soft-bounced) is only fully visible in the Resend dashboard, which the agent cannot open. The
notification_logtable status is also not surfaced in the dashboard UI.