# Design: Scheduled Synthesis Generation with Email Delivery **Date**: 2026-03-27 **Scope**: Per-theme scheduling (days + time), automatic generation + email to up to 3 addresses --- ## Context Users want automated weekly syntheses delivered by email without manually triggering generation. Each theme can have its own schedule (e.g. "AI News every Mon/Wed/Fri at 8am, emailed to 3 people"). The app already runs as a Docker daemon (`restart: unless-stopped`), so no external cron or daemon script is needed — the scheduler is an internal background task. --- ## 1. Data Model ### New table: `theme_schedules` ```sql CREATE TABLE theme_schedules ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), theme_id UUID NOT NULL UNIQUE REFERENCES themes(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, enabled BOOLEAN NOT NULL DEFAULT true, days JSONB NOT NULL DEFAULT '[]', time_utc TEXT NOT NULL DEFAULT '08:00', emails JSONB NOT NULL DEFAULT '[]', last_run_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX idx_theme_schedules_enabled ON theme_schedules(enabled) WHERE enabled = true; ``` - `theme_id` is UNIQUE — one schedule per theme (1:1) - `days`: JSON array of day codes `["mon","tue","wed","thu","fri","sat","sun"]` - `time_utc`: execution time in UTC, format `"HH:MM"` - `emails`: JSON array of up to 3 email addresses - `last_run_at`: prevents double runs on the same day --- ## 2. API Endpoints - `GET /api/v1/themes/:id/schedule` — get schedule for a theme (null if none) - `PUT /api/v1/themes/:id/schedule` — upsert schedule - `DELETE /api/v1/themes/:id/schedule` — remove schedule ### PUT body ```json { "enabled": true, "days": ["mon", "wed", "fri"], "time_utc": "08:00", "emails": ["user@example.com", "colleague@example.com"] } ``` ### Validation - `emails`: max 3, each must be valid email format - `days`: each must be one of `mon, tue, wed, thu, fri, sat, sun` - `time_utc`: must match `HH:MM` format (00:00-23:59) - Theme must belong to the authenticated user --- ## 3. Backend Scheduler ### Background task in `main.rs` ```rust // Scheduled synthesis generation (check every 60 seconds) tokio::spawn(async move { let mut interval = tokio::time::interval(Duration::from_secs(60)); loop { interval.tick().await; scheduler::run_scheduled_jobs(&state).await; } }); ``` ### `services/scheduler.rs` **`run_scheduled_jobs(state)`:** 1. Get current UTC time and day code (e.g. `"wed"`) 2. Query `theme_schedules` where: - `enabled = true` - `days` contains today's day code - `time_utc <= current_time` - `last_run_at` is NULL or `last_run_at::date < today` 3. For each due schedule: - Check `job_store.has_active_job(user_id)` — skip if manual generation in progress - Load theme settings - Run `run_generation_inner` with a dummy watch channel (no SSE client) - On success: send synthesis email to all addresses in `emails` - Update `last_run_at = now()` - Log success/failure **Error handling:** If generation or email fails, log the error and continue to the next schedule. Don't retry — the schedule will trigger again at the next interval if `last_run_at` wasn't updated. **Concurrency:** Jobs run sequentially (one at a time) to avoid overwhelming LLM rate limits. --- ## 4. Frontend — Schedule Section in ThemeManager New "Planification" section in the theme page, below sources. ### UI components: - **Enable toggle**: checkbox to enable/disable - **Day selector**: 7 buttons (L M M J V S D), toggle on click, selected = highlighted - **Time picker**: `` - **Email list**: up to 3 inputs with add/remove buttons - **Auto-save**: changes sent immediately via `PUT /api/v1/themes/:id/schedule` ### Section hidden when no theme is selected. --- ## 5. Files ### Backend - **Create:** `backend/migrations/20260327000030_create_theme_schedules.sql` - **Create:** `backend/src/models/schedule.rs` - **Create:** `backend/src/db/schedules.rs` - **Create:** `backend/src/handlers/schedules.rs` - **Create:** `backend/src/services/scheduler.rs` - **Modify:** `backend/src/models/mod.rs`, `db/mod.rs`, `handlers/mod.rs`, `services/mod.rs` - **Modify:** `backend/src/router.rs` — schedule routes - **Modify:** `backend/src/main.rs` — spawn scheduler task - **Modify:** `CLAUDE.md` — migration count ### Frontend - **Create:** `frontend/src/api/schedules.ts` - **Create:** `frontend/src/components/settings/SettingsSchedule.tsx` - **Modify:** `frontend/src/pages/ThemeManager.tsx` - **Modify:** `frontend/src/i18n/fr.ts` - **Modify:** `frontend/src/types.ts` ### Tests - **Create:** `backend/tests/api_schedules_test.rs` - **Modify:** `e2e/tests/themes.spec.ts`