BIO.RE
Notifications

Spectrum Smoke (admin) — End-to-end dispatch test

Fires one notification per event in SPECTRUM_SMOKE_EVENTS (26 events default) to a single recipient. Useful after a template change to verify rendering + provider delivery end-to-end. Channel overrides force email-only by default; admin can opt into push / web-push for FCM credential testing.

POST /api/v1/admin/notifications/spectrum-smoke — 👤 Admin · permission notification:manage · Throttle: 5 req / hour

Triggers NotificationService.send for every event in SPECTRUM_SMOKE_EVENTS (or a body-supplied subset) against the user matching to. Each successful call writes a NotificationJob row through the normal pipeline — same template renderer, same provider adapters, same critical-event policy. Used after a Spectrum email template change to verify rendering + outbound delivery end-to-end without waiting for organic traffic.

Critical events bypass channel overrides. 6 events (security_alert, security_alert_token_reuse, account_locked, payout_processed, payout_failed, payout_critical_failure) re-enable email + in_app regardless of the channels override block. This is exact production behaviour by design — the smoke test exercises the live resolveChannels() policy. So when smoke-testing pushes, expect to also see in-app rows for those 6 events.

Default = email-only. When channels is omitted, the smoke dispatch forces { email: true, push: false, inApp: false, webPush: false, sms: false } so it never leaks push / in-app notification rows during a routine template smoke. Wave 2 client-web calls pass { push: true, webPush: true, email: false } to validate FCM credentials end-to-end.

3 events deferred. SPECTRUM_SMOKE_DEFERRED lists 3 events (payment_successful, marketing_campaign, ticket_resolved) without production dispatchers today. They are excluded from the default smoke list to avoid drowning the test in errors.

Request

Body — SpectrumSmokeDto

FieldTypeRequiredValidationNotes
tostringIsEmailRecipient email. Must match an existing User.email (lowercased + trimmed). Resolved to user.id for the dispatch.
eventsstring[]ArrayMaxSize(50), IsString({ each: true })Subset of SPECTRUM_SMOKE_EVENTS to dispatch. Omit for the full 26-event default list. Unknown keys (no fixture) come back with status: 'no-fixture'.
localestringIsStringLocale override passed to the template renderer. Defaults to 'tr' so the recipient receives Turkish copy regardless of stored UserSettings.locale.
channelsobjectnested SpectrumSmokeChannelsDto{ email?, push?, inApp?, webPush?, sms? } booleans. Omitted flags default false. When the whole block is omitted, default = email-only.

Throttle / permission

LayerLimit
Permissionnotification:manage
Throttle5 req / hour
HTTP code200

Response

200 OKApiResponseOf<SpectrumSmokeResponseDto>

{
  "success": true,
  "data": {
    "to": "[email protected]",
    "userId": "usr-uuid-here",
    "count": 26,
    "queued": 24,
    "errors": 1,
    "results": [
      { "eventKey": "email_verification", "status": "queued" },
      { "eventKey": "password_reset", "status": "queued" },
      { "eventKey": "marketing_campaign", "status": "no-fixture" },
      { "eventKey": "kyc_result", "status": "error", "message": "Template render failed: missing variable" }
    ]
  }
}

Top-level fields

FieldTypeNotes
tostringEcho of the input email
userIdstring (UUID)Resolved User.id for the recipient
countnumberTotal events attempted (length of resolved events list)
queuednumberEvents where NotificationService.send resolved (job row written)
errorsnumberEvents where send threw
resultsarrayPer-event status breakdown

Per-event result item

FieldTypeNotes
eventKeystringThe event the smoke attempted
statusenumqueued (success), no-fixture (event has no entry in SPECTRUM_SMOKE_FIXTURES), error (send threw)
messagestringPresent only when status === 'error' — error message from the thrown exception

Errors

HTTPReason
400Invalid payload (bad email shape, events over 50 entries, etc.)
401Missing / invalid admin session
403Permission denied (notification:manage required)
404No User row found matching the to email
429Throttle exceeded (5 req / hour)

Side effects

  1. Lookup User by lowercased to email. 404 if missing.
  2. Resolve events — body subset or the 26-event default.
  3. Compute channelOverrides — body's channels block or { email: true, push: false, inApp: false, webPush: false, sms: false }.
  4. For each event:
    • Skip with status: 'no-fixture' if SPECTRUM_SMOKE_FIXTURES[eventKey] is missing.
    • Call NotificationService.send({ eventKey, userId, variables: fixtures[eventKey], locale, channelOverrides, entityRef: { type: 'spectrum_smoke', id: '${eventKey}:${Date.now()}' } }).
    • entityRef synthesized from timestamp so successive smoke runs for the same event produce distinct dedupeKeys.
    • On success → status: 'queued'. On throw → status: 'error' with message.
  5. Log a single summary line with masked email + counts (PII-safe).

Code samples

# Full smoke (26 events, email only, tr locale)
curl -X POST 'https://api.bio.re/api/v1/admin/notifications/spectrum-smoke' \
  -H 'Content-Type: application/json' \
  -H 'Cookie: admin_session=...' \
  -H 'X-CSRF-Token: ...' \
  -d '{ "to": "[email protected]" }'

# Push-only smoke (FCM credential validation)
curl -X POST 'https://api.bio.re/api/v1/admin/notifications/spectrum-smoke' \
  -H 'Content-Type: application/json' \
  -H 'Cookie: admin_session=...' \
  -H 'X-CSRF-Token: ...' \
  -d '{ "to": "[email protected]", "channels": { "push": true, "webPush": true, "email": false } }'

# Two-event subset, English locale
curl -X POST 'https://api.bio.re/api/v1/admin/notifications/spectrum-smoke' \
  -H 'Content-Type: application/json' \
  -H 'Cookie: admin_session=...' \
  -H 'X-CSRF-Token: ...' \
  -d '{ "to": "[email protected]", "events": ["email_verification", "password_reset"], "locale": "en" }'
type SpectrumSmokeResult = {
  to: string;
  userId: string;
  count: number;
  queued: number;
  errors: number;
  results: Array<{
    eventKey: string;
    status: 'queued' | 'no-fixture' | 'error';
    message?: string;
  }>;
};

async function spectrumSmoke(
  to: string,
  opts: { events?: string[]; locale?: string; channels?: Record<string, boolean> } = {},
): Promise<SpectrumSmokeResult> {
  const res = await fetch('https://api.bio.re/api/v1/admin/notifications/spectrum-smoke', {
    method: 'POST',
    credentials: 'include',
    headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrfToken },
    body: JSON.stringify({ to, ...opts }),
  });
  const json = await res.json();
  if (!res.ok || !json.success) {
    throw Object.assign(new Error(json?.error?.message ?? 'Smoke failed'), {
      code: json?.error?.code,
    });
  }
  return json.data;
}

Source

SourcePathLines
Controllerapps/api-core/src/modules/notification/admin-notification.controller.ts607–626 (spectrumSmoke)
Request DTOapps/api-core/src/modules/notification/dto/spectrum-smoke.dto.ts44–89 (SpectrumSmokeDto), 10–35 (SpectrumSmokeChannelsDto)
Response DTOapps/api-core/src/modules/notification/dto/spectrum-smoke.dto.ts94–131 (SpectrumSmokeResultItemDto, SpectrumSmokeResponseDto)
Serviceapps/api-core/src/modules/notification/admin-notification.service.ts1798–1883 (spectrumSmoke)
Smoke event listapps/api-core/src/modules/notification/spectrum-smoke-fixtures.ts39–73 (SPECTRUM_SMOKE_EVENTS, 26 entries)
Deferred eventsapps/api-core/src/modules/notification/spectrum-smoke-fixtures.ts80–84 (SPECTRUM_SMOKE_DEFERRED, 3 entries)
Variable fixturesapps/api-core/src/modules/notification/spectrum-smoke-fixtures.ts97+ (SPECTRUM_SMOKE_FIXTURES map)
Prisma modelpackages/prisma/prisma/schema.prismaNotificationJob (2203) — every smoke send writes a row
Live responseNOT verified — sourced from DTO at dto/spectrum-smoke.dto.ts:113–131

On this page