ReviewOS

stacks/analyticshq

[feature] Scheduled email reports / digests

#14
Closed glennmichael123 opened this 10 days ago ·
10 days ago

Gap

No email reports. app/Jobs is empty (.gitkeep); the only mail is the Stripe SubscriptionConfirmation. All competitors ship weekly/monthly digests.

Proposed

A scheduled job (via app/Scheduler.ts) that emails per-site weekly/monthly summaries (KPIs, top pages/sources, deltas). Opt-in per user/site.

Priority: P1.


From the 2026-07-20 privacy-first competitive audit (vs. Plausible / Fathom / Simple Analytics / Umami).

10 days ago

Foundation landed: scripts/analytics/digest.ts --site=<id> computes a site's N-day summary (visitors/views/sessions with % change vs the prior period + top pages/sources), verified against the live DB. Remaining: the scheduled weekly job that renders this into an email and sends it to each site owner — needs mail config wired + the framework to boot (currently blocked by a vendored-router/bun-version issue).

10 days ago

Half of this is already built.

scripts/analytics/digest.ts exists (65 lines) and already computes the digest payload. What is missing is only the delivery path:

  • Scheduling — app/Scheduler.ts:22-23 schedules scripts/analytics/prune.ts and nothing else. The digest is never run.
  • The mailer — template plus send.
  • Opt-in — a per-user or per-site preference, and an unsubscribe link.

So the remaining work is Scheduler + mailer + opt-in, not the report itself. Worth reflecting in the body so whoever picks it up does not rewrite the payload computation.

10 days ago

Shipped in cfcf5d6.

What landed

  • app/Jobs/SendAnalyticsDigest.ts, registered in app/Scheduler.ts — app/Jobs is no longer just a .gitkeep.
  • Weekly and monthly per-site summaries: visitors, views, sessions, top pages, top sources, each with a delta against the equally-long preceding window.
  • app/Analytics/summary.ts computes them from a plain site id and date range, because a scheduled job has no request to hand the route handlers.
  • Opt-in per site via settings.digest (weekly | monthly | off), settable through PATCH /api/sites/{siteId}. Absent means off.

Three decisions worth recording

Scheduled daily, not weekly and monthly. The job decides who is due — weekly on Monday, monthly on the 1st, both UTC — rather than this repo carrying one schedule per cadence. That keeps "who gets mail today" in one readable place, and makes a missed run cost a day instead of a month.

A period with no page views sends nothing. An email reporting zeroes to someone whose site is new or seasonal earns an unsubscribe at best and a spam report at worst, and carries nothing the reader did not already know.

Deltas render as null, not 0. "First week, nothing to compare" and "flat against last week" are different facts, and a template showing them identically tells the reader something untrue.

Found while building it: the mailer reports a refused SMTP connection as success: false rather than throwing, so the first version counted sent 1 against a dead server. The sender returns a boolean now and the job tracks sent/skipped/failed separately.

Deliberately not included

  • No digest UI — opt-in is API-only.
  • No email_suppressions table, so an unsubscribe is not enforced anywhere yet. That should be filed separately before this is pointed at real customer addresses.

Sign in to comment on this issue.