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

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:

Steps

Order rationale: the sequence must be created and active BEFORE the booking, because processEmailSequences only fires for bookings created while the sequence is_active = 1. The booking must be created AFTER the sequence so its creation timestamp falls inside the after_booking delay window on the next cron run.

  1. 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 after user= as <HOST_SLUG>. Expect: A non-empty URL like https://calendo.dev/booking/?user=<HOST_SLUG>. [L1]
  1. 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) to CU20 Email Test <RUNID>, leave Duration (#etDuration) at 30 min, and set Description (#etDescription) to CU20 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 name CU20 Email Test <RUNID>. [L1]
  1. 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 (expected cu20-email-test-<RUNID>). Use the actual observed value going forward. [L2] Capture screenshot: cu20-eventtype-created
  1. 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]
  1. Action: Click "+ Add Sequence" (#createSequenceBtn). The create form (#sequenceCreateForm) appears. Set Trigger (#seqTrigger) to "After booking" (value after_booking). Set Delay minutes (#seqDelay) to 0. Set Subject (#seqSubject) to CU20 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
  1. Action: Click the Create button (#seqSubmitBtn). Expect: A "Sequence created" toast; the create form hides; the sequence appears in #sequencesList as 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", meaning is_active = 1.) [L1]
  1. 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
  1. 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]
  1. 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-view visible). 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]
  1. 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-details contains the event name CU20 Email Test <RUNID>. [L1] Capture screenshot: cu20-booking-confirmed
  1. 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]
  1. 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 for Invitee CU20 <RUNID> on event CU20 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
  1. Action (L3, immediate confirmation email): Switch to the Gmail tab (https://mail.google.com). Search inv-<RUNID> (and if needed subject:(Confirmed CU20 Email Test <RUNID>)). Open the message whose subject is Confirmed: CU20 Email Test <RUNID> with <P1 host name>. Expect: The confirmation email is present (delivered within ~1-2 min of booking, sent from Calendo <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
  1. Action (L3, sequence email — cron-gated but near-term): The after_booking delay-0 sequence fires on the NEXT */5 cron run after the booking's created_at enters 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 search subject:(CU20 seq <RUNID>). Expect: A message arrives with subject CU20 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-sequence If 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.
  1. Action (host notification, optional L3): In Gmail search subject:(New booking: Invitee CU20 <RUNID>). Expect: The host notification email New 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]
  1. 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 when now is within ±5 minutes of exactly 24h (or 1h) before start_time (src/worker.js sendPendingReminders); subject will be Reminder: 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 whose start_time is ~1h05m in the future, then wait for the next */5 cron 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
  1. 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 to 24h before (value 24) and save the event type. Reconfirmation fires via sendPendingReconfirmations when start_time - 24h <= now < start_time and reconfirmation_sent_at IS NULL; subject Please 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 marks reconfirmed = 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 with reconfirmation_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 saves 24h before and 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
  1. Action: Re-open the event editor after a reload and confirm both Reminders and Reconfirmation settings persisted (#editEtReconfirmation still 24; reminder checkboxes still in their saved state). Expect: Settings survive reload (proves PUT /api/event-types/:id saved reconfirmation_hours and reminder_settings). [L2]

L3 reality checks

Perform these in the browser, in Gmail (https://mail.google.com) as ravikantguptaofficial@gmail.com:

Cleanup

Leave both accounts clean for the next run:

  1. 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 the Invitee 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 as subject:(Cancelled: CU20 Email Test <RUNID>).)
  2. Delete the email sequence: Settings tab -> Email Sequences card -> find the CU20 seq <RUNID> row in #sequencesList -> click Delete (the row's deleteSequence(...) button) -> confirm. Reload and verify it is gone.
  3. Delete the event type(s): Event Types tab -> delete CU20 Email Test <RUNID> (and CU20 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.)
  4. Gmail housekeeping (optional but preferred): In Gmail, select the RUNID messages (search inv-<RUNID> and subject:(CU20 seq <RUNID>) and subject:(CU20 Email Test <RUNID>)) and archive or trash them so the host inbox stays uncluttered for the next run.
  5. 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:

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

Manual residue / cannot-verify