← Back to list

I Built a Notification Platform Like an Adtech Engineer — Not a Martech Buyer

What years in audience systems taught me that most martech sales decks skip — plus a runnable demo you can try tonight.

Z N · 2026-07-03 03:15 · 0 claps · 7.4 min read
Open on Medium ↗
Wiki topics: AIM · AI in Marketing

I Built a Notification Platform Like an Adtech Engineer — Not a Martech Buyer

What years in audience systems taught me that most martech sales decks skip — plus a runnable demo you can try tonight.

Repo: github.com/zizimind/abc-notification-platform

System design: SYSTEM_DESIGN.md

OS notification

OS notification

TL;DR for busy leaders

Buy Braze for the journey UI. Own the audience layer — match rate, cost-per-reachable-user, incrementality — because that’s your moat, not your marketer’s drag-and-drop.

Most notification programs fail silently between matched, reachable, and delivered. Vendors show you the first number and hide the second.

The crossover: under ~500K MAU, buy. When reachability is the product, own the layer underneath and let Braze sit on top.

Runnable proof below — 14 services, real Web Push, one command.

The problem (product view)

Your product team publishes a campaign: “New episode — notify iView subscribers in Australia on app 4.2+”.

Marketing sees 4.2 million users matched. Finance approves the send budget. Engineering triggers the pipeline.

Three days later someone asks: how many people actually received it?

That gap — between matched, reachable, and delivered — is where most notification programs quietly fail. Martech tools excel at showing segment sizes and open rates. They rarely highlight match rate: the percentage of matched users who actually possessed a valid push token, email, or SMS endpoint at the exact time of the send.

I’ve worked on both sides of this fence: adtech (audiences, match rates, fraud, cost per reachable user) and martech(journeys, lifecycle email, Braze-style Canvas solutions). They are marketed and sold as completely different ecosystems. Under the hood, however, they share the exact same underlying pipeline core:

Find the right people → apply policy → send on the right channel → measure honestly.

I built an open-source proof-of-concept to demonstrate what this owned infrastructure looks like when built from scratch — and to be explicitly clear about what you’d still need to add before I’d sign off on it as an engineering leader.

Who this is for: Teams where reachability and precise measurement are the product core — media, streaming networks, marketplaces, and high-scale consumer apps. If you are under ~500K MAU with zero dedicated platform engineers, buying Braze or Iterable alone is entirely fine. This article is for the rest.

Try the demo in 2 minutes

make demo    # Launch 14 services: Kafka, Postgres, ClickHouse
make web     # Spin up the signup client at localhost:5173

Open your browser → sign up → click 🔔 Notify me → grant permissions → watch a real OS push notification land instantly.

This isn’t a mock setup. It uses real VAPID keys, a live Kafka backend, an active push worker, and a functional service worker passing through the full network path.

Signup with ABC Publish a new content and notify viewers

Signup with ABC Publish a new content and notify viewers

Publish a new content and notify viewers

Publish a new content and notify viewers

💡 Honest Scope Note: This demo intentionally seeds 20,000 mock users to remain laptop-friendly. The architecture itself targets 50M-user resolution via streaming batches, which I’ve stress-tested utilizing k6 rather than triggering a live 50M device broadcast. Web push and email flows are completely live; mobile app and SMS targets are simulated.

What happens when you click “Notify me” (engineering view)

The architecture breaks down into three decoupled slices:

  • Layer 1: Identity & ingest: The signup service writes to Postgres and logs an outbox entry within a single unified transaction. Debezium CDC streams these changes out to Kafka. A sync worker projects users into ClickHouse (optimized for heavy audience queries) and Redis (optimized for hot preferences and active tokens).
  • Layer 2: Publish, resolve & fan-out: When a campaign is published with structured rules (e.g., country, product tier, app version), a resolver streams matching user IDs in sequential batches of ≤1,000. This prevents millions of records from bloating application memory. An orchestrator evaluates policy constraints per user: deduping → channel matching → quiet hours check → frequency capping before fanning out events to dedicated channel Kafka topics.
  • Layer 3: Deliver & measure: Individual channel workers process messages at provider-safe limits using token-bucket egress shaping. Every milestone of the delivery funnel is recorded: sent → delivered → opened → clicked.

Note: The quick-start demo routes through POST /v1/push/test to trigger your stored subscription instantly—bypassing audience resolution so your local web test always functions. The broader broadcast flow traverses the entire end-to-end pipeline.

Full implementation notes can be found in the system design doc.

Three engineering problems every vendor slides past

1. Dual-write failures on signup

Writing directly to a database and then trying to manually publish an event to Kafka is a distributed systems anti-pattern. If your service crashes in between those actions, you drop data. I implemented a transactional outbox pattern combined with CDC — the system event commits alongside the user row, or the whole operation rolls back.

2. High-scale audience resolution

Evaluating criteria like “iView + AU + app version ≥ 4.2” across 30M users will crash your container if you load the entire array into memory. The adtech pattern handles this gracefully: stream batches continuously through Kafka instead of querying monolithic tables into application space.

3. Provider rate limits and backpressure

FCM, SES, and Twilio will throttle your endpoints aggressively if you spike traffic. Rather than allowing an in-memory queue to overflow on a Monday morning broadcast, Kafka holds the raw, unsent backlog while throttled workers drain messages at a strictly configured QPS.

Turn match rate into dollars — the metric product and eng should share

Every notification campaign interacts with three distinct audience layers. Standard product dashboards usually only show you the top layer:

Match Rate =. Registered ÷ Users Reachable Users ​= 2.8M / 4.2M = 67%

Put it in dollars: an unnoticed 68%→51% slide on a 4.2M segment is ~700K reachable users silently lost. At a 2% conversion and $40, that′s ~$560K/quarter evaporating— and it never shows up on a vendor dashboard, because the vendor only reports the users it could reach. A drop like that is a data problem (stale push tokens, un-synced bounces, wrong app version from the SDK) — not a copy problem.

This proof-of-concept maps these layers explicitly. In production, this should feed a real-time dashboard split by channel, alerting on week-over-week anomalies.

Who owns match rate? This is an org question as much as a technical one. Platform eng owns resolve, egress, and cost gates. Mobile/SDK owns token freshness — where match rate quietly dies. Product/growth owns the hypotheses and guardrails. The failure mode is that nobody owns the number end-to-end, so it rots a few points a quarter until a campaign underperforms and no one can say why. Name an owner before you build the dashboard.

What I’d add before hitting production

The POC validates the architecture; these are the core hardening steps required for an enterprise deployment:

Hypothesis-driven campaigns (Product)

Every single broadcast must ship with an explicit written hypothesis (“push notifications will outperform email for lapsed iView cohorts”), tracking primary metric (D7 retention), and concrete guardrail metrics (unsubscribe velocity, spam complaints). Don’t just A/B test email subject lines — test your frequency caps and channel mixes against true holdout groups that receive nothing to measure real baseline lift.

Shadow mode — break it before prod does (Engineering)

I learned this the expensive way. A one-character change — >= became > on an app-version filter- slipped through review because the preview count looked normal. Monday's broadcast went to ~800K unintended users before anyone noticed; the provider bill spiked and the complaint desk flagged it before our dashboards did. Shadow mode would have diffed the audience and caught the +800K in seconds. That incident is exactly why I now treat shadow-diff on segment changes as a non-negotiable CI gate, not a nice-to-have.

Before pushing any updates to your audience segment logic or global policies, run them through a dual-pipeline setup:

  1. Shadow resolve: Run the updated evaluation logic in parallel and diff the resulting user IDs against production outputs.
  2. Shadow orchestrate: Log every single drop, suppress, and defer decision to your data lake without executing the actual external send.

Estimated infrastructure costs (Finance)

A standard multi-channel broadcast across 4.2M registered targets easily scales to millions of deliverability actions.

  • Amazon SES Email (2.8M sends): ∼$280 (at standard $0.10 per 1K rates)
  • FCM Push Delivery: Compute worker runtime costs (∼$50–$150)
  • Kafka & ClickHouse Segment Scan Processing: ∼$100–$500 based on wide query parameters
  • Estimated Cost: ∼$500–$1,000 for a single massive multi-channel broadcast. Production architectures must surface pre-send cost warnings prior to execution.

Compliance ledger & opt-out guarantees (Legal)

You must enforce an immutable consent engine that logs lawful data processing bases per channel and guarantees global unsubscribe state propagation across all endpoints within a strict 24-hour window.

Braze or similar tools — use it, don’t worship it

Braze is an incredible tool for marketer self-service layouts, Canvas journey mapping, and robust client SDK maintenance. I will openly advocate for buying it whenever trumps absolute infrastructure ownership.

What it won’t give you out of the box:

  • Transparent granularity on your actual match rates.
  • Independent shadow pipeline testing for structural segment verification.
  • Deep programmable policy control over complex internal data structures.
  • Complete predictable cost modeling during massive scale broadcasts.

The ideal hybrid approach:

  • Own: Identity verification, segment compilation, match rate tracking, egress pacing engines, and your underlying core experimentation framework.
  • Buy (Braze): Journey builder UI, templating engines, localized in-app rendering blocks, and day-to-day marketing workspaces.
  • The Contract: Bind the systems together via a shared unique ID mapping (external_iduser_id), unified event tracking, and bidirectional real-time opt-out synchronization.

Closing

The open-source POC demonstrates that it is entirely achievable to resolve high-volume audiences, pace out deliveries safely to match provider throttling, and map your entire data funnel cleanly. You can pull the code and test it yourself tonight.

But shifting to enterprise production requires looking beyond the raw system design. It requires match rate accountability, real-time cost gates, and knowing exactly where your vendor toolset ends and where your core adtech-grade data layer needs to begin.

The next time you sync with your martech vendor, ask them one direct question: “What is our precise match rate today, and can we safely shadow-test segment structural changes?” If the room goes silent, you know exactly where your architectural gap lives.

Repo: github.com/zizimind/abc-notification-platform

System design: SYSTEM_DESIGN.md


메타데이터
post_id
9fe8ee2e97ba
slug
i-built-a-notification-platform-like-an-adtech-engineer-not-a-martech-buyer-9fe8ee2e97ba
url
https://medium.com/@zizimind/i-built-a-notification-platform-like-an-adtech-engineer-not-a-martech-buyer-9fe8ee2e97ba
canonical_url
https://medium.com/@zizimind/i-built-a-notification-platform-like-an-adtech-engineer-not-a-martech-buyer-9fe8ee2e97ba
author_url
https://medium.com/@zizimind
status
ok
fetched_at
2026-07-11 20:10:18