{"protocol":"clank-doc/1","frameworkVersion":"0.24.0","slug":"reminders-and-delivery","title":"Reminders, recurrence and notification delivery","description":"openReminders({ path, auth }) and createReminderClient(...) provide account owned reminder storage. Mount mountReminders(container, client) for creation, due/active/completed filters, editing, completion, snooze, deletion and refresh. Saves","group":{"id":"full-stack","title":"Full stack"},"url":"https://docs.clank.run/docs/reminders-and-delivery","source":"docs/reminders-and-delivery.md","headings":["Calendar recurrence","Delivery preferences and digests"],"tableOfContents":[{"id":"calendar-recurrence","title":"Calendar recurrence","level":2},{"id":"delivery-preferences-and-digests","title":"Delivery preferences and digests","level":2}],"markdown":"# Reminders, recurrence and notification delivery\n\n`openReminders({ path, auth })` and `createReminderClient(...)` provide account-owned reminder storage. Mount `mountReminders(container, client)` for creation, due/active/completed filters, editing, completion, snooze, deletion and refresh. Saves, completion and snooze require the displayed record version; stale edits preserve the draft and ask the user to refresh. Duplicate creation keys return the original reminder. Snooze uses server time and accepts 1–10,080 minutes. There are at most 200 reminders per account.\n\n## Calendar recurrence\n\n```ts\nimport { previewSchedule } from \"@clank.run/framework/schedules\";\n\nconst recurrence = {\n  frequency: \"weekly\" as const,\n  interval: 2,\n  startDate: \"2027-01-04\",\n  weekdays: [1, 3],\n  time: \"09:30\",\n  timeZone: \"America/New_York\",\n  exceptionDates: [\"2027-01-18\"],\n  endDate: \"2027-12-31\",\n};\nconst preview = previewSchedule(recurrence, { after: Date.now(), limit: 5 });\nawait reminders.save({\n  title: \"Review upcoming work\",\n  dueAt: preview.occurrences[0].at,\n  recurrence,\n});\n```\n\nRules support daily, weekly and monthly frequencies, intervals of 1–366, Sunday=0 through Saturday=6 weekdays, a monthly day, an inclusive end date and up to 366 exception dates. Calendar dates range from 1970 through January 1, 2100. A monthly day that does not exist is skipped. Local times that disappear during daylight-saving transitions are skipped; repeated minutes run once using `overlap: \"earlier\"` (default) or `\"later\"`. The preview reports skipped dates and when its bounded horizon is exhausted. `mountSchedulePreview` renders the same results as accessible text before saving.\n\nThe editor offers frequency, named time zone and exception dates. Advanced API rules keep their interval, weekday/month-day, end date and overlap policy when editing within the same frequency. Editing only the title preserves a snoozed due time. Changing only exceptions or the zone preserves the calendar anchor and picks the next eligible occurrence at or after the current due time. Changing the local date/time explicitly sets a new anchor. Completing a recurring reminder advances once to the next occurrence after its current due time; completion after the final occurrence marks it complete. Concurrent or replayed completion cannot advance twice. These reminders are durable personal records: mounting the panel does not create an operating-system or browser push notification.\n\n## Delivery preferences and digests\n\n`openNotificationCenter({ path, auth, categories, sendEmail })` stores in-app notifications and queues optional email. `sendEmail` receives an abort signal and stable `idempotencyKey`; pass that key to a provider that supports idempotency. Publication keys deduplicate per account. Workers call `workEmailOnce()` or `startEmailWorker()`; only accounts with verified email, an active account and current opt-in may receive email.\n\n```ts\nawait notifications.setPreference({\n  category: \"updates\",\n  inApp: true,\n  email: true,\n  delivery: \"daily\",\n  timeZone: \"America/Chicago\",\n  digestTime: \"09:00\",\n  quietHours: { start: \"22:00\", end: \"08:00\" },\n});\n```\n\nDelivery defaults to immediate, UTC, no quiet hours. Hourly digests wait for the next UTC hour boundary; daily digests use the named zone's local calendar and skip nonexistent local digest times. Quiet intervals may cross midnight and must have distinct start/end times. Clear them with `quietHours: null`. `nextNotificationDelivery` previews the earliest delivery instant. Newly enabled quiet hours are checked again when the worker runs, including retries. Disabling email or disabling the account prevents pending delivery. Changing frequency affects newly published notifications; existing jobs retain their scheduled instant, subject to current quiet hours and opt-in.\n\nA digest contains up to 50 currently due notifications from the same recipient/category. The first attempt persists its recipient, content, member IDs and delivery key, so a retry sends the same payload even after new notifications arrive or the service restarts. Recipient-email changes cancel that sealed batch. A failed delivery uses the durable job retry policy; after automatic attempts are exhausted the account can call `retryEmail(id)` or use **Retry failed email**. Quiet-hours deferrals keep the batch linked to its current job so manual retry remains available. Pending or failed delivery records are retained rather than silently discarded when enforcing the configured retention limit; publication fails when pending capacity is full.\n\n`mountNotificationCenter` displays read/unread state, scheduled delivery, delivery attempts and per-category channel/digest/zone/quiet-hour controls. If only one quiet-hour endpoint is entered, it preserves the unfinished form until both endpoints are provided. Rendering escapes notification contents and accepts only local navigation URLs.\n\n`tests/recurrence-ui.test.mjs` drives editor controls through real authenticated SQLite transport, including snoozed title edits, advanced-rule preservation, exception changes and stale conflicts. The schedule tests cover DST gaps/overlaps, half-hour transitions, monthly boundaries and owner isolation. Notification tests cover digest sealing, restart/retry identities, opt-out, deferred manual retries and calendar delivery policy.\n"}