diff --git a/specs/behaviors/events.md b/specs/behaviors/events.md index 7971341..7ebbaad 100644 --- a/specs/behaviors/events.md +++ b/specs/behaviors/events.md @@ -139,8 +139,8 @@ The "Add to Google Calendar" control is a prefilled `calendar.google.com/calenda Attendees get two reminder emails per event: **one week before** and **one day before** `startsAt`. - A background sweep runs in-process every 5 minutes (the same shape as the revocation sweeper in [authorization.md](authorization.md#revocation)). Single replica, so one sweep. It runs *inside* the API process rather than as a Kubernetes CronJob or an Actions cron because the bookkeeping write has to go through the in-process write mutex ([storage.md](storage.md)); a separate process could only write by pushing a commit to the data repo and waiting for hot reload, which is a roundabout way to set one timestamp. -- The week reminder is **due** for a `scheduled` event when `now ≥ startsAt − 7d` and `now < startsAt − 24h` and `reminders.weekSentAt` is unset. The day reminder is due when `now ≥ startsAt − 24h` and `now < startsAt` and `reminders.daySentAt` is unset. -- The windows are closed-ended so a pod that was down for the whole window, or an event created inside it, skips that reminder rather than sending a "one week" note two days out. An event created less than a day ahead gets no reminders; its confirmation email is the reminder. +- The week reminder is **due** for a `scheduled` event when `startsAt − 7d ≤ now < startsAt − 6d` and `reminders.weekSentAt` is unset. The day reminder is due when `startsAt − 24h ≤ now < startsAt − 18h` and `reminders.daySentAt` is unset. Each window is one sweep-friendly band just after its mark, not everything from the mark to the event. +- The windows are narrow on purpose: a pod that was down for the whole band, or an event created after the band passed, skips that reminder rather than sending a "one week" note three days out or a "tomorrow" note at lunchtime. An event created less than seven days ahead gets only the day reminder; one created less than a day ahead gets none — its confirmation email is the reminder. - **At-most-once:** the sweep records `reminders.SentAt = now` on the event *before* sending, in its own commit under the write mutex, then emails each going attendee. A send failure is logged and not retried; a duplicate reminder is the worse failure. - Each reminder email goes to the attendee's `PrivateProfile.email` and includes the when/where block, the member's follow-up answer if any ("You said you're coming to work on SquadQuest"), the canonical URL, and a "Can't make it? Update your RSVP" link. The `.ics` is not re-attached — the confirmation already delivered it. - Members who RSVP after a reminder was sent don't receive that reminder. They were just on the page.