Skip to content

feat(notifications): add scheduled email reports - #1840

Merged
github-actions[bot] merged 1 commit into
Smartdevs17:mainfrom
retkatmun:feat/scheduled-email-reports-1758
Oct 19, 2026
Merged

github-actions[bot] merged 1 commit into
Smartdevs17:mainfrom
retkatmun:feat/scheduled-email-reports-1758

Conversation

@retkatmun

Copy link
Copy Markdown
Contributor

Closes #1758

Summary

Merchants can now schedule recurring analytics summary emails (daily, weekly, or monthly) delivered via the existing EmailProvider (SendGrid or SES). Previously reports had to be pulled manually.

Changes

New files:

File Purpose
backend/services/notification/scheduledReportService.ts Core service: schedule CRUD, recipient management, email rendering, sending, and runDue() batch processing. Includes InMemoryReportStore with an injectable ReportStore interface for swap-in persistence.
backend/services/notification/scheduledReportRouter.ts Plain Node.js HTTP router mounted at /scheduled-reports. Routes: POST (create), GET (list by merchant), GET/:id, PATCH/:id, DELETE/:id, POST/:id/recipients, DELETE/:id/recipients, POST/:id/send-now.
backend/services/notification/jobs/scheduledReportJob.ts Polling/cron integration: createScheduledReportPoller() (lightweight setInterval loop, 15-min default), registerScheduledReportJob() (WeightedFairQueue registration), createScheduledReportHandler() (queue handler factory).
backend/services/notification/__tests__/scheduledReportService.test.ts 49 unit tests covering all public API paths.

Modified files:

  • backend/server.ts — wires the service, router, and poller into the server lifecycle. The poller is stopped cleanly on shutdown. Snapshot factory is stubbed with zeros (production should wire to SubscriptionAnalyticsService.compute()).

Design decisions

  • Decoupled scheduling from email delivery. runDue() accepts a snapshotFor() callback so the service has no dependency on the analytics layer — easy to test and swap implementations.
  • Partial failure tolerance. sendReport() iterates recipients individually; one bad address doesn't block others. Errors are collected and returned in the result.
  • Clock-drift safety. nextSendAt() normalises to the start of the hour so minor drift doesn't cause a full-period skip.
  • Report types: analytics_summary, mrr_breakdown, churn_report, full_report — each renders different sections.
  • XSS hardening. escapeHtml() applied to all user-controlled content in HTML output. The subject line is escaped once (not double-escaped).
  • Persistence is pluggable. InMemoryReportStore is the default; a real DB store can be injected via the ReportStore interface without touching the service.

Acceptance criteria

  • Schedule configurable — frequency (daily/weekly/monthly), report type, start time, enable/disable
  • Recipients managed — add/remove via API, deduplicated and normalised to lowercase
  • Tested — 49 unit tests, all passing

What was tested

npx jest --config jest.backend.config.js \
  --testMatch '**/backend/services/notification/__tests__/scheduledReportService.test.ts'

Result: 49 passed, 0 failed.

Test coverage includes: nextSendAt() for all frequencies, schedule CRUD validation, recipient management, email rendering for all report types, HTML escaping, send with partial failures, post-send timestamp advance, runDue() skipping disabled/empty schedules, and snapshot error isolation.

Implements issue Smartdevs17#1758. Merchants can now schedule recurring analytics
summary emails (daily / weekly / monthly) delivered via the existing
EmailProvider (SendGrid or SES).

New files
  backend/services/notification/scheduledReportService.ts
    - ScheduledReportService: CRUD for schedules, recipient management,
      buildReportEmail() (pure renderer), sendReport(), runDue()
    - InMemoryReportStore with injectable ReportStore interface for swap-in
    - nextSendAt() helper with clock-drift-safe hour normalisation
    - Report types: analytics_summary, mrr_breakdown, churn_report, full_report

  backend/services/notification/scheduledReportRouter.ts
    - Plain Node.js HTTP router (no framework) mounted at /scheduled-reports
    - POST / GET (list) / GET /:id / PATCH /:id / DELETE /:id
    - POST /:id/recipients  DELETE /:id/recipients
    - POST /:id/send-now (ad-hoc trigger)

  backend/services/notification/jobs/scheduledReportJob.ts
    - createScheduledReportPoller() — lightweight setInterval loop (15 min)
    - registerScheduledReportJob() — registers with WeightedFairQueue
    - createScheduledReportHandler() — queue handler factory

  backend/services/notification/__tests__/scheduledReportService.test.ts
    - 49 unit tests; all passing

Modified
  backend/server.ts — wires the service, router, and poller into the
    server lifecycle; poller stopped cleanly on shutdown

Closes Smartdevs17#1758
@drips-wave

drips-wave Bot commented Oct 19, 2026

Copy link
Copy Markdown

@retkatmun Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@github-actions
github-actions Bot merged commit 857d604 into Smartdevs17:main Oct 19, 2026
11 of 30 checks passed
@retkatmun
retkatmun deployed to security-review October 19, 2026 14:05 — with GitHub Actions Active
@retkatmun
retkatmun deployed to security-review October 19, 2026 14:10 — with GitHub Actions Active

This branch was successfully deployed

1 active deployment
security-review — d01258b4 Deployed Oct 19, 2026 by retkatmun via Security Dashboard #1394
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add scheduled email reports

1 participant