TypeDrop
2026-06-21 Challenge
2026-06-21
Medium
Typed Notification Dispatcher with Retry & Channel Routing
You're building the notification layer for a user-facing SaaS app. Raw notification payloads arrive as `unknown` from an internal queue; your dispatcher must validate them, route them to the correct typed channel handler (email, SMS, push), fan out deliveries concurrently, and return a strongly-typed delivery report — with zero `any`.
Goals
- Define a three-variant discriminated union `NotificationPayload` and a `ChannelRegistry` mapped type that pairs each channel key with its exact handler type using `Extract`.
- Implement `parsePayload` to narrow `unknown` to `NotificationPayload` at runtime using type guards — no `as` assertions.
- Implement `dispatchOne` to route a payload to its correct handler and retry on failure up to `maxRetries` additional times, always resolving to a `DeliveryStatus`.
- Implement `dispatchAll` to fan out all dispatches concurrently with `Promise.allSettled`, then aggregate results into a fully-typed `DeliveryReport` with per-channel summaries.
challenge.ts
// Core types & main function signatures
type EmailPayload = { channel: "email"; id: string; userId: string; to: string; subject: string; body: string };
type SmsPayload = { channel: "sms"; id: string; userId: string; to: string; text: string };
type PushPayload = { channel: "push"; id: string; userId: string; deviceToken: string; title: string; body: string };
type NotificationPayload = EmailPayload | SmsPayload | PushPayload;
type ChannelHandler<T extends NotificationPayload> = (payload: T) => Promise<void>;
// ChannelRegistry maps each channel key → the handler for that exact variant
type ChannelRegistry = { [K in NotificationPayload["channel"]]: ChannelHandler<Extract<NotificationPayload, { channel: K }>> };
// Main entry point
async function dispatchAll(
rawPayloads: unknown[],
registry: ChannelRegistry,
maxRetries: number,
): Promise<DeliveryReport> { /* TODO */ }
Hints (click to reveal)
Hints
- For `ChannelRegistry`, a mapped type `{ [K in NotificationPayload["channel"]]: ChannelHandler<Extract<NotificationPayload, { channel: K }>> }` lets the compiler verify each handler receives the right payload variant.
- In `parsePayload`, narrow step-by-step: first check `typeof raw === 'object' && raw !== null`, then check the `channel` field is a string, then branch on its value — the compiler will track the type in each branch.
- In `dispatchOne`, a `for` loop from `0` to `maxRetries` (inclusive) lets you attempt the call and `break` on success, capturing the last error for the `failed` status.
Useful resources
Or clone locally
git clone -b challenge/2026-06-21 https://github.com/niltonheck/typedrop.git