/** * @module notification.healthcheck * * Types describing the outcome of a notification delivery health check — a self-serve * diagnosis of why a given user is or is not receiving notifications on each delivery method. * * A health check produces one {@link NotificationDeliveryHealthCheckResult} per delivery method * ({@link NotificationDeliveryMethod}), each carrying zero or more {@link NotificationHealthCheckIssue} * findings and, optionally, a {@link NotificationHealthCheckProbe} describing a real test message * that was dispatched through that method. * * Issue codes are intentionally open-ended: {@link KnownNotificationHealthCheckIssueCode} covers the * checks the library performs itself, while apps and delivery providers are free to emit their own * codes for provider-specific findings (e.g. an address on a Mailgun suppression list). */ import { type Maybe, type Minutes, type Seconds } from '@dereekb/util'; import { type NotificationTemplateType } from './notification.id'; /** * A delivery method (channel) that notifications can be sent through. * * The values mirror the per-method flags on {@link NotificationBoxRecipientTemplateConfig} * (`se`/`st`/`sp`/`sn`), so a method maps directly onto the config field that gates it. */ export declare enum NotificationDeliveryMethod { /** * Email delivery. Gated by `se`. */ EMAIL = "e", /** * Text/SMS delivery. Gated by `st`. */ TEXT = "t", /** * Push notification delivery. Gated by `sp`. */ PUSH = "p", /** * In-app delivery to a NotificationSummary. Gated by `sn`. */ NOTIFICATION_SUMMARY = "n" } /** * All delivery methods, in the order a report should present them. */ export declare const ALL_NOTIFICATION_DELIVERY_METHODS: NotificationDeliveryMethod[]; /** * A value held per delivery method, for the methods it is known for. * * Partial because a health check only covers the methods it was asked about, so anything derived * from one covers those methods only. * * @template T - The per-method value. */ export type NotificationDeliveryMethodMap = Partial>; /** * The outcome of a health check, or of one individual finding within it. */ export declare enum NotificationHealthCheckStatus { /** * Everything that was checked looks healthy. */ OK = "ok", /** * Something looks off, but delivery is probably still working. */ WARNING = "warn", /** * Delivery is blocked or broken. */ ERROR = "error", /** * Waiting on an asynchronous result, such as an in-flight delivery probe. */ PENDING = "pending", /** * Not checked. Either the method is not configured for this app, or no check was available. */ SKIPPED = "skipped", /** * The check ran but could not reach a conclusion, such as when a provider API was unreachable. */ UNKNOWN = "unknown" } /** * Severity ranking used to roll several statuses up into one. * * Higher wins. `ERROR` outranks `PENDING` so that a method with a known problem is not masked by an * unrelated in-flight probe, and `SKIPPED` ranks lowest so an unconfigured method never drags a * report down. */ export declare const NOTIFICATION_HEALTH_CHECK_STATUS_SEVERITY: Record; /** * Rolls a set of statuses up into the single most severe one. * * @param statuses - The statuses to roll up. * @returns The most severe status, or {@link NotificationHealthCheckStatus.SKIPPED} if none were given. * * @example * ```ts * rollupNotificationHealthCheckStatus([NotificationHealthCheckStatus.OK, NotificationHealthCheckStatus.ERROR]); * // NotificationHealthCheckStatus.ERROR * ``` */ export declare function rollupNotificationHealthCheckStatus(statuses: NotificationHealthCheckStatus[]): NotificationHealthCheckStatus; /** * True if the status represents a problem the user should act on. * * @param status - The status to test. * @returns True for errors and warnings; false for statuses that need no action. */ export declare function isProblemNotificationHealthCheckStatus(status: NotificationHealthCheckStatus): boolean; /** * Issue codes emitted by the health checks the library performs itself. * * Apps and delivery providers may emit additional codes of their own — see * {@link NotificationHealthCheckIssueCode}. */ export declare enum KnownNotificationHealthCheckIssueCode { /** * The server has no send service configured for this delivery method, so nothing will ever be sent through it. */ SEND_SERVICE_NOT_CONFIGURED = "sendServiceNotConfigured", /** * No send service health check is available for this delivery method, so only configuration was inspected. */ SEND_SERVICE_HEALTH_CHECK_UNAVAILABLE = "sendServiceHealthCheckUnavailable", /** * No delivery target could be resolved for this method — e.g. no email address on the auth record and no override. */ NO_DELIVERY_TARGET = "noDeliveryTarget", /** * The recipient has opted out of all notifications. */ RECIPIENT_OPTED_OUT = "recipientOptedOut", /** * The recipient's notifications are disabled. */ RECIPIENT_DISABLED = "recipientDisabled", /** * This delivery method is switched off by the user's global or default configuration. */ METHOD_DISABLED_GLOBALLY = "methodDisabledGlobally", /** * This delivery method is switched off for the notification template type that was checked. */ METHOD_DISABLED_FOR_TEMPLATE = "methodDisabledForTemplate", /** * This delivery method is switched off for one or more of the user's individual notification boxes. */ METHOD_DISABLED_FOR_BOX = "methodDisabledForBox", /** * The user is not subscribed to any notification boxes, so no model-driven notifications will reach them. */ NO_NOTIFICATION_BOXES = "noNotificationBoxes", /** * The user has notification box exclusions that suppress notifications from matching boxes. */ NOTIFICATION_BOX_EXCLUSIONS = "notificationBoxExclusions", /** * The user's configuration has not finished syncing to their notification boxes. */ NEEDS_CONFIG_SYNC = "needsConfigSync", /** * One or more of the user's subscriptions is broken and will never send. */ SUBSCRIPTION_BROKEN = "subscriptionBroken", /** * One or more of the user's subscriptions has not finished being set up, so its notifications are delayed. */ SUBSCRIPTION_NOT_READY = "subscriptionNotReady", /** * A test message was dispatched and its outcome is not known yet. */ PROBE_PENDING = "probePending", /** * A test message was confirmed delivered. */ PROBE_DELIVERED = "probeDelivered", /** * A test message failed to deliver. */ PROBE_FAILED = "probeFailed", /** * A test message could not be dispatched at all. */ PROBE_DISPATCH_FAILED = "probeDispatchFailed" } /** * The code identifying what a {@link NotificationHealthCheckIssue} describes. * * Library checks use {@link KnownNotificationHealthCheckIssueCode}; apps and delivery providers may * use any other string to describe provider-specific findings. */ export type NotificationHealthCheckIssueCode = KnownNotificationHealthCheckIssueCode | string; /** * Structured detail attached to a {@link NotificationHealthCheckIssue}. * * Stored directly in Firestore, so values must be Firestore-compatible and should be kept small. */ export type NotificationHealthCheckIssueData = Readonly>; /** * A single finding produced by a health check. * * Field abbreviations: * - `c` — issue code * - `s` — status/severity * - `m` — human-readable message * - `f` — suggested fix * - `d` — structured detail */ export interface NotificationHealthCheckIssue { /** * Identifies what was found. See {@link KnownNotificationHealthCheckIssueCode}. */ c: NotificationHealthCheckIssueCode; /** * How severe the finding is. */ s: NotificationHealthCheckStatus; /** * Human-readable statement of what was found, suitable for showing to the affected user. */ m: string; /** * What the user (or an admin) can do about it, when there is a known remedy. */ f?: Maybe; /** * Structured detail backing the finding, for display or debugging. */ d?: Maybe; } /** * The human-facing content of a {@link NotificationHealthCheckIssue}. */ export interface NotificationHealthCheckIssueContent { /** * Human-readable statement of what was found, suitable for showing to the affected user. */ readonly message: string; /** * What the user (or an admin) can do about it, when there is a known remedy. */ readonly fix?: Maybe; /** * Structured detail backing the finding, for display or debugging. */ readonly data?: Maybe; } /** * Creates a {@link NotificationHealthCheckIssue}. * * @param code - The issue code. * @param status - The severity. * @param content - The message, plus an optional suggested fix and structured detail. * @returns The issue. * * @example * ```ts * notificationHealthCheckIssue(KnownNotificationHealthCheckIssueCode.NO_DELIVERY_TARGET, NotificationHealthCheckStatus.ERROR, { * message: 'There is no email address on your account.', * fix: 'Add an email address to your account.' * }); * ``` */ export declare function notificationHealthCheckIssue(code: NotificationHealthCheckIssueCode, status: NotificationHealthCheckStatus, content: NotificationHealthCheckIssueContent): NotificationHealthCheckIssue; /** * A real test message dispatched through a delivery method in order to observe whether it arrives. * * Delivery confirmation is asynchronous for most providers, so a probe is recorded when it is * dispatched and resolved on a later health check run. * * Field abbreviations: * - `id` — provider correlation id * - `at` — dispatch time * - `s` — current status * - `tg` — delivery target * - `d` — provider detail */ export interface NotificationHealthCheckProbe { /** * Provider-specific id used to correlate the dispatched message with its delivery outcome. * For email this is the provider's message id. */ id: string; /** * When the probe was dispatched. */ at: Date; /** * The probe's current status. {@link NotificationHealthCheckStatus.PENDING} until the provider * reports an outcome. */ s: NotificationHealthCheckStatus; /** * The delivery target the probe was sent to. */ tg: string; /** * Provider detail about the outcome, once known — typically the failure reason. */ d?: Maybe; } /** * True if the probe is still awaiting an outcome and should be re-checked. * * @param probe - The probe to test, if one exists. * @returns True if the probe is pending. */ export declare function isPendingNotificationHealthCheckProbe(probe: Maybe): boolean; /** * The correlation id of a probe the provider gave nothing to track it by. * * Empty rather than absent because the field is what a provider looks the outcome up with, and there is * nothing to look up — such a probe is always recorded already settled, so it is never queried. */ export declare const UNTRACKABLE_NOTIFICATION_HEALTH_CHECK_PROBE_ID = ""; /** * Records a dispatch attempt the provider gave no way to track. * * A send that produced no correlation id still happened, and the test message window is derived from the * recorded probe — so an attempt recorded as nothing at all would leave the server's throttle and the * client's countdown with nothing to key on, making the action look successful and be immediately * repeatable. Always settled ({@link NotificationHealthCheckStatus.UNKNOWN} when the provider accepted it, * `ERROR` when it did not), never pending: there is no outcome coming for it. * * @param probe - The attempt's time, settled status, target, and provider detail. * @returns The probe recording the attempt. */ export declare function untrackableNotificationHealthCheckProbe(probe: Omit): NotificationHealthCheckProbe; /** * The result of checking a single delivery method. * * Field abbreviations: * - `me` — delivery method * - `s` — rolled-up status * - `tg` — resolved delivery target * - `is` — findings * - `pr` — delivery probe * - `pb` — whether a test message can be dispatched through this method */ export interface NotificationDeliveryHealthCheckResult { /** * The delivery method that was checked. */ me: NotificationDeliveryMethod; /** * The most severe status across this method's findings and probe. */ s: NotificationHealthCheckStatus; /** * The delivery target that was resolved for this method — an email address, phone number, or * NotificationSummary id. Absent when none could be resolved. */ tg?: Maybe; /** * Everything the check found for this method. */ is: NotificationHealthCheckIssue[]; /** * The probe dispatched through this method, if any. May still be pending. */ pr?: Maybe; /** * Whether a test message can actually be dispatched through this method — the provider exposes a * probe and a delivery target was resolved to send it to. * * Whether probing is supported is only knowable on the server, so it is reported here for a client * that offers a "send a test message" action: absent means no, and the action should not be offered. */ pb?: Maybe; } /** * A complete health check result, covering every delivery method that was checked. * * Field abbreviations: * - `at` — when the check ran * - `vat` — when its pending probes were last verified * - `s` — rolled-up status across the whole check * - `t` — notification template type the configuration was evaluated against * - `is` — account-wide findings * - `m` — per-method results */ export interface NotificationHealthCheck { /** * When the check was run. */ at: Date; /** * When the check's pending probes were last verified. * * A verify-only run refreshes the probe findings without re-running the check, so it advances this * rather than `at` — otherwise polling an in-flight test message would keep pushing the user's own * run window out. Absent until a verify-only run has happened. */ vat?: Maybe; /** * The most severe status across the account-wide findings and every checked method. */ s: NotificationHealthCheckStatus; /** * The notification template type the per-template configuration was evaluated against. */ t?: Maybe; /** * Findings that apply to the account as a whole rather than to one delivery method — a disabled * account, a subscription that failed to sync, an opt-out that suppresses every method. */ is: NotificationHealthCheckIssue[]; /** * The result for each delivery method that was checked. */ m: NotificationDeliveryHealthCheckResult[]; } /** * Rolls a delivery method's findings and probe up into a single status. * * @param result - The per-method result, without its rolled-up status. * @returns The most severe status across the method's findings and probe. */ export declare function rollupNotificationDeliveryHealthCheckResultStatus(result: Pick): NotificationHealthCheckStatus; /** * The delivery methods whose test message is still awaiting an outcome. * * A pending probe is the whole reason to keep watching a check: it is the one part of a report that * changes on its own, as the provider records what happened to the message. Both the client (which * polls until they settle) and the server (which only consults a provider it has something to ask * about) scope their work to exactly these methods. * * @param healthCheck - The health check to read. * @returns The methods carrying a pending probe, in report order. Empty when nothing is in flight. */ export declare function notificationHealthCheckPendingProbeMethods(healthCheck: Maybe>): NotificationDeliveryMethod[]; /** * Finds the result for a specific delivery method. * * @param healthCheck - The health check to read. * @param method - The delivery method to look for. * @returns The method's result, or undefined if the method was not checked. */ export declare function notificationDeliveryHealthCheckResultForMethod(healthCheck: Maybe, method: NotificationDeliveryMethod): Maybe; /** * Every issue in a health check, account-wide findings first. * * @param healthCheck - The health check to read. * @returns All issues, account-wide first and then in method order. */ export declare function allNotificationHealthCheckIssues(healthCheck: Maybe): NotificationHealthCheckIssue[]; /** * Rolls a whole health check up into a single status. * * @param healthCheck - The check's account-wide findings and per-method results. * @returns The most severe status across everything the check found. */ export declare function rollupNotificationHealthCheckResultStatus(healthCheck: Pick): NotificationHealthCheckStatus; /** * How long a user must wait between health check runs. * * A run calls out to each delivery provider and can dispatch real messages, so it is throttled against * the check stored on the NotificationUser rather than being runnable on demand. */ export declare const DEFAULT_NOTIFICATION_USER_HEALTH_CHECK_THROTTLE_MINUTES: Minutes; /** * Input for {@link notificationUserHealthCheckNextRunAt}. */ export interface NotificationUserHealthCheckNextRunAtInput { /** * The check currently stored on the NotificationUser, if any. */ readonly healthCheck?: Maybe>; /** * Overrides {@link DEFAULT_NOTIFICATION_USER_HEALTH_CHECK_THROTTLE_MINUTES}. */ readonly throttleMinutes?: Maybe; } /** * The earliest time another health check may be run for a NotificationUser. * * Both the server (which enforces the throttle) and the client (which disables the action until then) * derive the window from the same stored check, so the UI cannot offer a run the server would reject. * * @param input - The stored check, and optionally a throttle window to use instead of the default. * @returns The time the next run is allowed, or undefined when no check has been run yet. */ export declare function notificationUserHealthCheckNextRunAt(input: NotificationUserHealthCheckNextRunAtInput): Maybe; /** * How long a user must wait between test messages on a single delivery method. * * Throttled separately from — and more strictly than — a plain run: a probe delivers a real message to * the user, while a run only reads configuration and provider state. Running the check must therefore * not consume the test message allowance, and vice versa. * * Only the default. An app that wants a different cadence declares its own value and passes it to both * the server (which enforces the window) and the client (which counts down to it) — see * {@link NotificationUserHealthCheckNextProbeAtInput.throttleMinutes}. Both sides must use the same * value, or the UI will offer a test message the server rejects. */ export declare const DEFAULT_NOTIFICATION_USER_HEALTH_CHECK_PROBE_THROTTLE_MINUTES: Minutes; /** * Input for {@link notificationUserHealthCheckNextProbeAt}. */ export interface NotificationUserHealthCheckNextProbeAtInput { /** * The check currently stored on the NotificationUser, if any. */ readonly healthCheck?: Maybe>; /** * The delivery methods the window is being computed for. * * Absent or empty considers every method that was checked, which is the window an unscoped test * message run answers to. */ readonly methods?: Maybe; /** * Overrides {@link DEFAULT_NOTIFICATION_USER_HEALTH_CHECK_PROBE_THROTTLE_MINUTES}. */ readonly throttleMinutes?: Maybe; } /** * The earliest time another test message may be dispatched for a NotificationUser. * * The window is per delivery method: each method has its own test message action, so a test email must * not hold the test text message off. Pass the methods being probed to get their window — the most * recent probe among just those methods — and pass none for the window across every checked method. * * Both the server (which enforces the window) and the client (which disables the action until then) * derive it from the same stored check, so the UI cannot offer a test message the server would reject. * * @param input - The stored check, the methods being probed, and optionally a throttle window to use * instead of the default. * @returns The time the next probe is allowed, or undefined when none of those methods has ever * dispatched one. */ export declare function notificationUserHealthCheckNextProbeAt(input: NotificationUserHealthCheckNextProbeAtInput): Maybe; /** * How long a caller must wait between verifications of a check's pending probes. * * A verify-only run exists to be polled: a dispatched test message settles on the provider's schedule, * so something has to keep asking until it does. It is throttled far more loosely than a run or a probe * because it is far cheaper — it consults a provider only for a method with a probe actually in flight, * and touches nothing else — but it is throttled all the same, so a client cannot poll a provider's API * in a tight loop. * * Seconds rather than minutes: the point of the verification is that the outcome appears while the user * is still looking at the report. */ export declare const DEFAULT_NOTIFICATION_USER_HEALTH_CHECK_VERIFY_THROTTLE_SECONDS: Seconds; /** * Input for {@link notificationUserHealthCheckNextVerifyAt}. */ export interface NotificationUserHealthCheckNextVerifyAtInput { /** * The check currently stored on the NotificationUser, if any. */ readonly healthCheck?: Maybe>; /** * Overrides {@link DEFAULT_NOTIFICATION_USER_HEALTH_CHECK_VERIFY_THROTTLE_SECONDS}. */ readonly throttleSeconds?: Maybe; } /** * The earliest time a check's pending probes may be verified again. * * Derived from `vat` rather than `at`, so verifying an in-flight test message neither answers to nor * consumes the user's run window. Both the server (which enforces the window) and the client (which * paces its polling to it) derive it the same way. * * @param input - The stored check, and optionally a window to use instead of the default. * @returns The time the next verification is allowed, or undefined when none has happened yet. */ export declare function notificationUserHealthCheckNextVerifyAt(input: NotificationUserHealthCheckNextVerifyAtInput): Maybe; /** * The earliest time another test message may be dispatched through each of a check's delivery methods. * * The per-method form of {@link notificationUserHealthCheckNextProbeAt}, for a UI that renders one test * message action per method and needs every method's window at once. * * @param input - The stored check, and optionally a throttle window to use instead of the default. * @returns The time the next probe is allowed on each checked method. A method that has never * dispatched a probe maps to undefined. */ export declare function notificationUserHealthCheckNextProbeAtByMethod(input: Omit): NotificationDeliveryMethodMap>; /** * Firestore sub-object converter for {@link NotificationHealthCheckIssue}. */ export declare const firestoreNotificationHealthCheckIssue: import("../..").FirestoreSubObjectFieldMapFunctionsConfig, any>>>; /** * Firestore sub-object converter for {@link NotificationHealthCheckProbe}. */ export declare const firestoreNotificationHealthCheckProbe: import("../..").FirestoreSubObjectFieldMapFunctionsConfig, any>>>; declare const notificationHealthCheckProbeMapFunctions: import("@dereekb/util").ModelMapFunctions, any>>>; /** * The Firestore data form of a {@link NotificationHealthCheckProbe}. */ export type NotificationHealthCheckProbeData = ReturnType; /** * Firestore sub-object converter for {@link NotificationDeliveryHealthCheckResult}. */ export declare const firestoreNotificationDeliveryHealthCheckResult: import("../..").FirestoreSubObjectFieldMapFunctionsConfig, any>>>; /** * Firestore sub-object converter for {@link NotificationHealthCheck}. */ export declare const firestoreNotificationHealthCheck: import("../..").FirestoreSubObjectFieldMapFunctionsConfig, any>>>; declare const notificationHealthCheckMapFunctions: import("@dereekb/util").ModelMapFunctions, any>>>; /** * The Firestore data form of a {@link NotificationHealthCheck}. */ export type NotificationHealthCheckData = ReturnType; /** * Field config for the optional {@link NotificationHealthCheck} stored on a NotificationUser. * * Unlike {@link firestoreNotificationHealthCheck}, this leaves the field absent instead of defaulting * to an empty check, so "never run" stays distinguishable from "ran and found nothing". */ export declare const optionalFirestoreNotificationHealthCheck: import("../..").FirestoreModelFieldMapFunctionsConfig, Maybe, any>>>>; export {};