Staff Newsletter — Code Map
API
| File | Role |
|---|---|
apps/api/src/staff-newsletter/staff-newsletter.module.ts | Wiring |
.../staff-newsletter-config.service.ts | Read/merge/upsert, recipient + org-id lookups |
.../staff-newsletter.controller.ts | GET/PATCH /organizations/:orgId/staff-newsletter |
.../dto/update-staff-newsletter.dto.ts | Per-event cadence patch; a type-level assertion fails the build if the catalog grows a field the DTO lacks |
.../staff-newsletter.listener.ts | @OnEvent × 6 — the immediately path |
.../staff-newsletter-digest.service.ts | Window queries, one per enabled event |
.../staff-newsletter-cron.service.ts | @ScheduledJob daily + weekly, advisory locks, audit claims, isoWeekKey |
.../staff-newsletter-sender.service.ts | Render + deliver per recipient; dashboard URL builders |
apps/api/src/notifications/templates/staff-newsletter.ts | Both shapes: staffNewsletterDigestEmail, staffNewsletterSingleEmail |
Producers touched
New domain events, each declared next to its producer:
| Event | Contract file | Emitted from |
|---|---|---|
subscription.cancellation_requested | apps/api/src/subscriptions/retention-events.ts | RetentionCaseService.openMemberCase (kind request only) |
subscription.debt_opened | apps/api/src/payments/subscription-events.ts | RecurringChargeService.handleChargeFailure, WebhookProcessingService, CancellationTailSettlementService.applyOutcome — all three reach the debt_since NULL→timestamp transition |
payment.charge_failed | apps/api/src/payments/subscription-events.ts | PaymentTransactionService.recordChargeOutcome — the one place every charge path reaches a terminal status |
Existing events reused unchanged: lead.created,
conversation.inbound_received. newMember listens to
membership.activation_confirmed, emitted by the member activation gate
(apps/api/src/member-activation-gate/) from membership.activated.
Modified
| File | Change |
|---|---|
apps/api/src/subscriptions/retention-case.service.ts | Emits the cancellation event; the old runDailyDigest is deleted |
apps/api/src/subscriptions/cancellation-cron.service.ts | The old handleRetentionDigest job is deleted; its stale schedule is removed at boot by the scheduled-jobs processor |
apps/api/src/notifications/cancellation-notifications.service.ts | retentionDigestForStaff and its template are deleted |
apps/api/src/payments/services/recurring-charge.service.ts | Selects s.debt_since so a new debt can be told from a growing one; emits |
apps/api/src/payments/services/webhook-processing.service.ts | debtEntry carries the event; emitted after commit |
apps/api/src/payments/services/cancellation-tail-settlement.service.ts | Captures inside the transaction, emits after |
apps/api/src/payments/services/payment-transaction.service.ts | Emits at the choke point; the prior-status read now also guards the bus |
apps/api/src/audit/audit-actions.ts | STAFF_NEWSLETTER_SENT |
Shared
| File | Role |
|---|---|
libs/shared/src/lib/schemas/staff-newsletter.ts | Catalog, defaults, cadenceFor / cadencesFor / eventsAtCadence / mergeStaffNewsletterConfig, zod schema |
libs/db/src/lib/schema/staff-newsletter.ts | staff_newsletter_configs |
libs/shared/src/lib/i18n/dictionaries/{en,he,ru}.json | emails.staffNewsletter.*, settings.notifications.* |
Web
| File | Role |
|---|---|
apps/web/src/app/[lang]/(protected)/dashboard/settings/(shell)/notifications/page.tsx | The screen: one cadence select per event |
apps/web/src/hooks/use-staff-newsletter.ts | GET + PATCH |
apps/web/src/hooks/use-settings-routes.ts | Adds the notifications route (the Updates tab), gated on organization:manage |
Tests
| File | Covers |
|---|---|
libs/shared/src/lib/schemas/staff-newsletter.unit.spec.ts | Defaults are the retention sections only; digest-only rejection; merge semantics |
apps/api/src/staff-newsletter/staff-newsletter.listener.unit.spec.ts | The gate order, never-throwing, item shapes |
apps/api/src/staff-newsletter/staff-newsletter-cron.service.unit.spec.ts | Claim-before-send, ISO-week keys across year boundaries, empty/locked/per-org isolation |
apps/api/src/notifications/templates/staff-newsletter.unit.spec.ts | Section dropping, retention variants, per-recipient money, RTL, subject escaping |
apps/web/.../notifications/settings-notifications-page.int.spec.tsx | Partial PATCH, digest-only options hidden, coach bounced, admin allowed |
Config
| Env | Purpose |
|---|---|
FRONTEND_URL | Dashboard links; absent, rows render without a link rather than throwing |
CRONS_ENABLED | Whether this process runs the two digest jobs |