Allow users to choose digest schedule (time of day, day of week, timezone) #28

Merged
matt merged 1 commit from mailship-200-allow-users-to-choose-digest-schedule-ti-60c into main 2026-09-23 15:57:56 +00:00
Collaborator

mailship-200

Users currently receive digests on a fixed schedule: daily at 17:00 UTC, weekly on Monday at 17:00 UTC. This PR lets each user choose the time of day for their daily digest, and the time + day of week for their weekly digest, with per-user IANA timezone support.

DB migration — adds hour (INTEGER DEFAULT 17), minute (INTEGER DEFAULT 0), day_of_week (INTEGER, nullable), and timezone (TEXT DEFAULT 'UTC') columns to the subscriptions table. Existing rows backfill to 17:00 UTC — unchanged behaviour. The migration is idempotent: new tables get the columns from CREATE TABLE; existing databases run ALTER TABLE ADD COLUMN with duplicate-column error suppression.

Cron & worker — getCronExpression now accepts an optional dayOfWeek parameter for weekly cron shapes. The digest worker reads the stored hour/minute/dayOfWeek/timezone from each subscription when creating the cron job and passes the IANA timezone to CronJob.from() instead of the hardcoded 'UTC'. Changing a subscription (via the existing registerSubscription path) reschedules the running job immediately.

API — PUT /subscription/email accepts optional hour (0–23), minute (0–59), dayOfWeek (1=Mon…7=Sun, only valid for weekly frequency), and timezone (IANA, validated via Intl.DateTimeFormat). Omitted fields fall back to the defaults (17:00 UTC). GET /subscription/email returns the schedule fields in its response.

How to test — pnpm run test:unit runs the full suite including the new test/schedule-fields.test.ts (9 tests covering custom inserts, cron shaping, job registration, and field preservation through updates). Manual acceptance: PUT {email, frequency:"daily", hour:7, minute:30, timezone:"America/New_York"} fires the daily job at 07:30 wall-clock in that zone; PUT {frequency:"weekly", dayOfWeek:6, hour:9} fires Saturday 09:00.

mailship-200 Users currently receive digests on a fixed schedule: daily at 17:00 UTC, weekly on Monday at 17:00 UTC. This PR lets each user choose the time of day for their daily digest, and the time + day of week for their weekly digest, with per-user IANA timezone support. **DB migration** — adds `hour` (INTEGER DEFAULT 17), `minute` (INTEGER DEFAULT 0), `day_of_week` (INTEGER, nullable), and `timezone` (TEXT DEFAULT 'UTC') columns to the `subscriptions` table. Existing rows backfill to 17:00 UTC — unchanged behaviour. The migration is idempotent: new tables get the columns from CREATE TABLE; existing databases run ALTER TABLE ADD COLUMN with duplicate-column error suppression. **Cron & worker** — `getCronExpression` now accepts an optional `dayOfWeek` parameter for weekly cron shapes. The digest worker reads the stored hour/minute/dayOfWeek/timezone from each subscription when creating the cron job and passes the IANA timezone to `CronJob.from()` instead of the hardcoded `'UTC'`. Changing a subscription (via the existing `registerSubscription` path) reschedules the running job immediately. **API** — `PUT /subscription/email` accepts optional `hour` (0–23), `minute` (0–59), `dayOfWeek` (1=Mon…7=Sun, only valid for weekly frequency), and `timezone` (IANA, validated via `Intl.DateTimeFormat`). Omitted fields fall back to the defaults (17:00 UTC). `GET /subscription/email` returns the schedule fields in its response. **How to test** — `pnpm run test:unit` runs the full suite including the new `test/schedule-fields.test.ts` (9 tests covering custom inserts, cron shaping, job registration, and field preservation through updates). Manual acceptance: `PUT {email, frequency:"daily", hour:7, minute:30, timezone:"America/New_York"}` fires the daily job at 07:30 wall-clock in that zone; `PUT {frequency:"weekly", dayOfWeek:6, hour:9}` fires Saturday 09:00.
hudson added 1 commit 2026-09-23 14:51:50 +00:00
feat: allow users to choose digest schedule (hour, minute, dayOfWeek, timezone)
All checks were successful
CI / checks (pull_request) Successful in 44s
e34f1cd821
Add DB migration (hour, minute, day_of_week, timezone columns) with
idempotent ALTER TABLE upgrade path for existing databases.

Extend getCronExpression with optional dayOfWeek parameter; weekly
defaults to Monday (1) when not specified.

Worker createJob now passes stored schedule fields to cron expression
and uses the subscription's IANA timezone instead of hardcoded 'UTC'.

PUT /subscription/email accepts optional hour (0-23), minute (0-59),
dayOfWeek (1-7, only for weekly), timezone (IANA). GET response
includes the new fields. Omitted fields fall back to defaults (17:00
UTC).

Closes mailship-200
matt approved these changes 2026-09-23 15:57:53 +00:00
matt merged commit 5007a79a3a into main 2026-09-23 15:57:56 +00:00
matt deleted branch mailship-200-allow-users-to-choose-digest-schedule-ti-60c 2026-09-23 15:58:08 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: matt/mailship#28
No description provided.