Skip to content
AB

Building a Notification Platform Instead of Sending Push Messages Everywhere

Centralize notification policy, preferences, scheduling, delivery, and idempotency behind one backend capability.

Ashish BhakhandPublished

Directly calling a push provider from every feature starts simple. Then each feature implements preferences, quiet hours, retries, templates, invalid tokens, analytics, and duplicate prevention differently.

I prefer one notification capability. Features request an intent; the notification system decides how and when to deliver it.

Code
type NotificationIntent = {
  id: string;
  userId: string;
  kind: "lesson_reminder" | "milestone_earned";
  data: Record<string, string>;
  requestedAt: string;
};

The intent describes meaning. It does not contain Firebase-specific fields or a fully rendered device message.

Separate policy from delivery#

The pipeline can perform distinct steps:

  1. load user preferences and timezone;
  2. decide whether the intent is eligible;
  3. select channels and schedule time;
  4. render a versioned template;
  5. deliver through a provider adapter;
  6. record the outcome.

Separating these steps makes behavior explainable. “Skipped because reminders are disabled” is different from “push provider timed out.”

Code
interface PushGateway {
  send(message: PushMessage): Promise<DeliveryResult>;
}

Firebase, APNs, or another provider stays behind the gateway. Provider error codes are translated into outcomes such as retryable failure, invalid token, or permanent rejection.

Give every delivery an identity#

Use a stable key such as {intentId}:{channel} and enforce uniqueness. A queue retry should not send the same milestone notification twice.

Scheduled notifications also need cancellation and replacement. If a user's reminder time changes, update the pending schedule instead of adding another message.

Treat tokens as changing infrastructure#

Device tokens expire and rotate. Store multiple devices per user, update last-seen information, and disable a token when the provider identifies it as invalid. Token cleanup is part of delivery correctness.

Track operational outcomes such as queued, delivered-to-provider, retrying, skipped, and failed. “Accepted by provider” is not the same as “read by user,” so name metrics honestly.

A notification platform costs more than one provider call, but it prevents every product feature from becoming a partial notification system. Centralized policy produces consistent user behavior and far better operational control.

← Back to all writing