Skip to main content
Task notification emails are skipped when the current task due date is strictly more than 24 hours before the delivery check. Exactly 24 hours overdue, future due dates and tasks without a due date remain eligible under the existing recipient, notification-age and preference checks. This is an instant-based cutoff, not a calendar-day or workspace-timezone comparison. The dispatcher loads the current task through its task list and workspace board, binding both the notification task ID and workspace ID. It does not use the queued due-date snapshot for eligibility. A task rescheduled into the future is therefore not skipped because its notification contains an older due date. Immediate and digest dispatchers recheck every email item before rendering. Mixed digests omit overdue items and build their subject and content from the remaining items. An all-overdue batch completes with skipped delivery logs and makes no email provider request. The skip reason is skipped: task_due_older_than_1_day. A tenant-scoped lookup that succeeds with an explicitly null task row is a known unavailable task (for example, deleted or moved). It is skipped with skipped: task_unavailable, so other eligible items in a mixed batch can proceed. Undefined or malformed lookup results remain unknown and fail closed. Skip and settlement writes must succeed before the dispatcher continues. If an email provider was already attempted, a later settlement error remains an unknown outcome for reconciliation; the digest is not marked retryable or automatically resent. A provider success: false result can represent a transport timeout and does not prove rejection. Existing explicit suppression reasons still settle as skipped; other unsuccessful or thrown provider outcomes require reconciliation. The same settlement protection applies to push delivery because the write helpers are shared: accepted or ambiguous push attempts remain in processing for reconciliation. Only zero deliveries with every device rejected as invalid proves no push was accepted. Push freshness eligibility is unchanged. Missing task/workspace bindings, conflicting payload task IDs, tenant mismatch, lookup errors and invalid current due dates fail before delivery. They are not recorded as a proven overdue skip. Existing delivery-attempt and ambiguous provider-result handling remain in place; freshness is not reevaluated after a provider attempt. Push and non-task notifications retain their existing checks. For source validation, run the focused notification freshness and dispatcher tests, then bun check and a real web app build through the resource queue. The digest route is first-class under apps/web/src/app/api/cron/; after route registration changes, run node scripts/tanstack-migration-manifest.js generate followed by node scripts/tanstack-migration-manifest.js check and bun web:api-routes:check. The package command bun migration:tanstack:manifest remains disabled while the alternate runtime is paused; source inventory generation does not resume that runtime. Rust route parity remains backlog and does not mean production traffic has moved. Source checks do not prove production activation or delivered email. No migration or manual notification replay is required by this change.

Retained batch reconciliation

The digest persists delivery_in_flight in the processing batch before calling a provider. The write must return the exact processing batch ID; zero updated rows or database errors prevent provider contact. A settlement or ambiguous attempt error writes the fixed reconciliation_required marker while keeping status processing. If that write fails, the cron returns an error; the earlier in-flight marker remains the durable unknown-outcome signal. Neither marker is a provider acceptance receipt. Each authorized digest cron also performs a bounded read-only inspection of up to 50 marked processing batches in the root workspace, with a separate two-second discovery budget capped by the overall processing deadline. The budget is checked before each query; an in-flight query may exceed it. It reports reconciliation entries with batch IDs, persisted log counts and terminal_log_snapshot. At most 1001 logs are read per batch; empty, oversized, pending, failed, missing-date or ambiguous-error snapshots never qualify. Null-workspace batches and older unmarked processing rows require separate tenant-bound operator inspection. This inspection never sends a notification, updates logs, resets a batch or automatically marks a batch sent. A true snapshot only means every observed log is already sent with a valid settlement timestamp and either no error or an explicit skipped: reason. It is not provider acceptance or inbox arrival proof, and it cannot exclude a concurrently started queue insert. For an authorized reconciliation, first select the exact processing batch by ID and its marker through an authenticated, tenant-bound read-only client. Capture all attached delivery log IDs, statuses, timestamps and skip reasons. Resolve any active queue/dispatcher ownership before treating the snapshot as complete. Only when every log is already terminal sent/skipped, the complete batch membership is settled, and the corresponding outcome is known may an explicitly authorized operator prepare a separately reviewed batch-only status update, guarded by its ID, current processing status and unchanged marker. Use the complete log count; do not change delivery logs or contact a provider during that operation. The current helper has no conditional reconciliation guard; do not call it as a reconciliation shortcut. The cron does not provide that mutation capability. Pending logs after an attempted provider call require correlated provider outcome receipts and a separately reviewed settlement operation. Unknown/accepted outcomes remain held: do not use Retry, mark failed/pending, clear the marker, or resend. If receipts or complete membership are unavailable, retain processing and record the unresolved batch ID. This change adds no automatic unknown-outcome recovery, new schema or production migration.