Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesStaff NewsletterStaff Newsletter — Code Map

Staff Newsletter — Code Map

API

FileRole
apps/api/src/staff-newsletter/staff-newsletter.module.tsWiring
.../staff-newsletter-config.service.tsRead/merge/upsert, recipient + org-id lookups
.../staff-newsletter.controller.tsGET/PATCH /organizations/:orgId/staff-newsletter
.../dto/update-staff-newsletter.dto.tsPer-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.tsWindow queries, one per enabled event
.../staff-newsletter-cron.service.ts@ScheduledJob daily + weekly, advisory locks, audit claims, isoWeekKey
.../staff-newsletter-sender.service.tsRender + deliver per recipient; dashboard URL builders
apps/api/src/notifications/templates/staff-newsletter.tsBoth shapes: staffNewsletterDigestEmail, staffNewsletterSingleEmail

Producers touched

New domain events, each declared next to its producer:

EventContract fileEmitted from
subscription.cancellation_requestedapps/api/src/subscriptions/retention-events.tsRetentionCaseService.openMemberCase (kind request only)
subscription.debt_openedapps/api/src/payments/subscription-events.tsRecurringChargeService.handleChargeFailure, WebhookProcessingService, CancellationTailSettlementService.applyOutcome — all three reach the debt_since NULL→timestamp transition
payment.charge_failedapps/api/src/payments/subscription-events.tsPaymentTransactionService.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

FileChange
apps/api/src/subscriptions/retention-case.service.tsEmits the cancellation event; the old runDailyDigest is deleted
apps/api/src/subscriptions/cancellation-cron.service.tsThe old handleRetentionDigest job is deleted; its stale schedule is removed at boot by the scheduled-jobs processor
apps/api/src/notifications/cancellation-notifications.service.tsretentionDigestForStaff and its template are deleted
apps/api/src/payments/services/recurring-charge.service.tsSelects s.debt_since so a new debt can be told from a growing one; emits
apps/api/src/payments/services/webhook-processing.service.tsdebtEntry carries the event; emitted after commit
apps/api/src/payments/services/cancellation-tail-settlement.service.tsCaptures inside the transaction, emits after
apps/api/src/payments/services/payment-transaction.service.tsEmits at the choke point; the prior-status read now also guards the bus
apps/api/src/audit/audit-actions.tsSTAFF_NEWSLETTER_SENT

Shared

FileRole
libs/shared/src/lib/schemas/staff-newsletter.tsCatalog, defaults, cadenceFor / cadencesFor / eventsAtCadence / mergeStaffNewsletterConfig, zod schema
libs/db/src/lib/schema/staff-newsletter.tsstaff_newsletter_configs
libs/shared/src/lib/i18n/dictionaries/{en,he,ru}.jsonemails.staffNewsletter.*, settings.notifications.*

Web

FileRole
apps/web/src/app/[lang]/(protected)/dashboard/settings/(shell)/notifications/page.tsxThe screen: one cadence select per event
apps/web/src/hooks/use-staff-newsletter.tsGET + PATCH
apps/web/src/hooks/use-settings-routes.tsAdds the notifications route (the Updates tab), gated on organization:manage

Tests

FileCovers
libs/shared/src/lib/schemas/staff-newsletter.unit.spec.tsDefaults are the retention sections only; digest-only rejection; merge semantics
apps/api/src/staff-newsletter/staff-newsletter.listener.unit.spec.tsThe gate order, never-throwing, item shapes
apps/api/src/staff-newsletter/staff-newsletter-cron.service.unit.spec.tsClaim-before-send, ISO-week keys across year boundaries, empty/locked/per-org isolation
apps/api/src/notifications/templates/staff-newsletter.unit.spec.tsSection dropping, retention variants, per-recipient money, RTL, subject escaping
apps/web/.../notifications/settings-notifications-page.int.spec.tsxPartial PATCH, digest-only options hidden, coach bounced, admin allowed

Config

EnvPurpose
FRONTEND_URLDashboard links; absent, rows render without a link rather than throwing
CRONS_ENABLEDWhether this process runs the two digest jobs