6dfaa45061
* feat(notifications): opt-in daily "nytt att bokföra" email digest Users asked for an email when new work arrives: bank transactions that synced overnight and documents that landed in the inbox. Adds a daily 05:45 UTC cron (after the 05:00 bank sync) that emails opted-in users a per-company summary with counts only, no amounts (data-minimization stance of the kvittens/skattekonto mails). - notification_settings.email_digest_enabled, NOT NULL DEFAULT false: strictly opt-in via a new toggle in the notification settings panel - notification_log type 'bookkeeping_digest' with the claim-then-send partial unique index pattern: one mail per user per company per day - counts unbooked transactions and unprocessed inbox items created in the last 24h; empty digests are never sent - brand-aware sender + link base via lib/email/brand-sender - docker crontabs regenerated from vercel.json Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122MznXxrLRyT96fGhfyzD4 * fix(notifications): digest review findings in one pass Skeptic + bot findings on PR #2078, resolved together: - Count queries now match the canonical worklist anchors: ignored transactions excluded; inbox items already booked directly or matched to a transaction (created_journal_entry_id / matched_transaction_id) no longer counted (skeptic: spurious digests). - Memberships sweep and member-email lookups chunk .in() id lists at 150 ids to stay under proxy URL limits (HTTP 414 at ~350 opted-in users). - Recoverable claim lifecycle (CodeRabbit): claim inserts as 'pending', flips to 'sent' only after the provider accepted the mail; a stale pending claim is atomically taken over by a later run, so a worker death mid-send no longer swallows the day's digest. New migration 20260831110000 admits 'pending' to the delivery_status CHECK. - Company name sanitized against CRLF header injection before the mail subject (compliance swarm ASVS V1.2.5), with test. - RoPA entry for the new processing activity in .compliance/ropa.yaml (compliance swarm GDPR Art. 30). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122MznXxrLRyT96fGhfyzD4 * fix(types): admit 'pending' to NotificationLog delivery_status union Matches migration 20260831110000; surfaced by fix re-verification. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0122MznXxrLRyT96fGhfyzD4 --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
115 lines
2.7 KiB
JSON
115 lines
2.7 KiB
JSON
{
|
|
"buildCommand": "NODE_OPTIONS=--max-old-space-size=6144 npm run build",
|
|
"regions": ["arn1"],
|
|
"crons": [
|
|
{
|
|
"path": "/api/deadlines/status/cron",
|
|
"schedule": "0 6 * * *"
|
|
},
|
|
{
|
|
"path": "/api/invoices/recurring/cron",
|
|
"schedule": "0 * * * *"
|
|
},
|
|
{
|
|
"path": "/api/settings/booking-templates/sync/cron",
|
|
"schedule": "30 4 * * *"
|
|
},
|
|
{
|
|
"path": "/api/tax-deadlines/cron",
|
|
"schedule": "0 0 * * *"
|
|
},
|
|
{
|
|
"path": "/api/extensions/enable-banking/sync/cron",
|
|
"schedule": "0 5 * * *"
|
|
},
|
|
{
|
|
"path": "/api/extensions/stripe/transactions/cron",
|
|
"schedule": "30 3 * * *"
|
|
},
|
|
{
|
|
"path": "/api/extensions/woocommerce/orders/cron",
|
|
"schedule": "45 3 * * *"
|
|
},
|
|
{
|
|
"path": "/api/extensions/shopify/orders/cron",
|
|
"schedule": "15 3 * * *"
|
|
},
|
|
{
|
|
"path": "/api/documents/verify/cron",
|
|
"schedule": "0 3 * * *"
|
|
},
|
|
{
|
|
"path": "/api/sandbox/cleanup/cron",
|
|
"schedule": "0 4 * * *"
|
|
},
|
|
{
|
|
"path": "/api/events/cleanup/cron",
|
|
"schedule": "0 2 * * *"
|
|
},
|
|
{
|
|
"path": "/api/extensions/cloud-backup/auto-sync/cron",
|
|
"schedule": "0 * * * *"
|
|
},
|
|
{
|
|
"path": "/api/idempotency/cleanup/cron",
|
|
"schedule": "30 * * * *"
|
|
},
|
|
{
|
|
"path": "/api/pending-operations/expire/cron",
|
|
"schedule": "30 2 * * *"
|
|
},
|
|
{
|
|
"path": "/api/extensions/skatteverket/skattekonto/sync/cron",
|
|
"schedule": "0 4 * * *"
|
|
},
|
|
{
|
|
"path": "/api/extensions/skatteverket/agi/kvittenser/cron",
|
|
"schedule": "*/15 * * * *"
|
|
},
|
|
{
|
|
"path": "/api/extensions/skatteverket/vat/kvittenser/cron",
|
|
"schedule": "30 */2 * * *"
|
|
},
|
|
{
|
|
"path": "/api/webhooks/dispatch/cron",
|
|
"schedule": "* * * * *"
|
|
},
|
|
{
|
|
"path": "/api/extensions/whatsapp-inbox/sweep/cron",
|
|
"schedule": "* * * * *"
|
|
},
|
|
{
|
|
"path": "/api/extensions/whatsapp-inbox/retention/cron",
|
|
"schedule": "15 4 * * *"
|
|
},
|
|
{
|
|
"path": "/api/extensions/invoice-inbox/sweep/cron",
|
|
"schedule": "*/2 * * * *"
|
|
},
|
|
{
|
|
"path": "/api/extensions/invoice-inbox/underlag-reconcile/cron",
|
|
"schedule": "45 5 * * *"
|
|
},
|
|
{
|
|
"path": "/api/bookkeeping/accruals/post-due/cron",
|
|
"schedule": "15 5 * * *"
|
|
},
|
|
{
|
|
"path": "/api/receipt-hunt/cron",
|
|
"schedule": "30 5 * * *"
|
|
},
|
|
{
|
|
"path": "/api/notifications/bookkeeping-digest/cron",
|
|
"schedule": "45 5 * * *"
|
|
},
|
|
{
|
|
"path": "/api/peppol/inbound/cron",
|
|
"schedule": "*/10 * * * *"
|
|
},
|
|
{
|
|
"path": "/api/peppol/outbound/status/cron",
|
|
"schedule": "5,20,35,50 * * * *"
|
|
}
|
|
]
|
|
}
|