← Back to all projects

LEARN PUSH NOTIFICATIONS DEEP DIVE

Learn Push Notifications: From Zero to Building Your Own Push Infrastructure

Goal: Deeply understand push notification systems—from basic polling to building production-grade infrastructure that rivals APNs/FCM. Master real-time communication, message delivery guarantees, encryption, and distributed systems.


Why Push Notifications Are Fascinating

Push notifications solve one of computing’s most interesting problems: How do you tell a device about something when it’s not actively listening?

The naive answer (“just send it!”) ignores brutal realities:

  • Battery life: A phone can’t maintain 50 active connections to 50 apps
  • Network efficiency: Mobile networks hate persistent connections
  • Reliability: What happens when the device is offline?
  • Scale: How does Apple deliver billions of notifications per day?

After completing these projects, you will:

  • Understand every layer from HTTP polling to encrypted push protocols
  • Know why platforms like APNs and FCM exist (and their tradeoffs)
  • Be able to build real-time systems that scale
  • Implement end-to-end encryption for messages
  • Design and build your own push notification infrastructure

Core Concept Analysis

The Evolution of “Push”

1. Polling (1990s)           → Client asks "anything new?" every N seconds
                              ↓
2. Long Polling (2000s)      → Client asks, server holds connection until data arrives
                              ↓
3. Server-Sent Events        → One-way persistent stream from server
                              ↓
4. WebSockets (2011)         → Full-duplex persistent connection
                              ↓
5. Push Services (2009+)     → Centralized infrastructure (APNs, FCM, Web Push)

The Push Notification Architecture

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  Your Backend   │────►│  Push Service   │────►│  User's Device  │
│  (App Server)   │     │  (APNs/FCM/     │     │  (iOS/Android/  │
│                 │     │   Mozilla)      │     │   Browser)      │
└─────────────────┘     └─────────────────┘     └─────────────────┘
        │                       │                       │
        │   HTTP/2 + Auth       │   Single persistent   │
        │   + Encryption        │   connection per OS   │
        └───────────────────────┴───────────────────────┘

Why Can’t You Just Connect Directly?

The crucial insight: A phone can maintain ONE connection to a push service, but not connections to every app.

  • APNs maintains a single TLS connection (port 5223) to every iOS device
  • All apps share this one connection
  • The push service multiplexes messages for different apps over this single pipe
  • This saves battery and network resources

Fundamental Concepts

  1. Device Token / Registration
    • Device registers with push service, gets unique token
    • App sends token to your backend
    • Your backend uses token to address messages
  2. Message Payload
    • JSON structure with notification data
    • Limited size (4KB for FCM, 4KB for APNs)
    • Can be “alert” (visible) or “silent” (background)
  3. Message Encryption (Web Push)
    • End-to-end encryption using ECDH + AES-128-GCM
    • Push service can’t read message content
    • Uses VAPID for server identification
  4. Delivery Guarantees
    • At-most-once vs at-least-once
    • Store-and-forward for offline devices
    • TTL (Time To Live) for message expiration

Project 1: HTTP Polling Chat (See Why It’s Terrible)

  • File: LEARN_PUSH_NOTIFICATIONS_DEEP_DIVE.md
  • Main Programming Language: JavaScript (Node.js)
  • Alternative Programming Languages: Python, Go, Rust
  • Coolness Level: Level 1: Pure Corporate Snoozefest
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 1: Beginner
  • Knowledge Area: HTTP / Real-time Communication
  • Software or Tool: Express.js / HTTP Server
  • Main Book: “TCP/IP Illustrated, Volume 1” by W. Richard Stevens

What you’ll build: A chat application where clients poll the server every 2 seconds asking “any new messages?” You’ll see firsthand why this approach wastes resources and creates latency.

Why it teaches push notifications: You must experience the pain before appreciating the solution. This project makes you feel why polling is inefficient—watching hundreds of pointless requests and seeing the delay between sending and receiving messages.

Core challenges you’ll face:

  • Managing message state → maps to understanding what “new” means
  • Handling concurrent requests → maps to stateless vs stateful servers
  • Measuring wasted bandwidth → maps to why push services exist
  • Observing latency → maps to the fundamental polling tradeoff

Key Concepts:

  • HTTP Request/Response: “TCP/IP Illustrated” Chapter 11 - Stevens
  • REST API Design: “Design and Build Great Web APIs” Chapter 3 - Amundsen
  • Stateless Protocol Limitations: RFC 2616 - HTTP/1.1 Specification

Difficulty: Beginner Time estimate: Weekend Prerequisites: Basic JavaScript, understanding of HTTP, can run a Node.js server

Real world outcome:

Terminal 1 (Server):
$ node polling-server.js
Server running on port 3000
[12:00:00] GET /messages?since=0 - 200 (0 messages)
[12:00:02] GET /messages?since=0 - 200 (0 messages)
[12:00:04] GET /messages?since=0 - 200 (0 messages)
[12:00:05] POST /messages - 201 (new message)
[12:00:06] GET /messages?since=0 - 200 (1 message)  ← 1 second delay!
[12:00:08] GET /messages?since=1 - 200 (0 messages)
...

Stats after 1 minute with 2 clients:
- Total requests: 60
- Requests with data: 2
- Wasted requests: 58 (96.7%!)

Implementation Hints:

The core insight is tracking “what has the client already seen?”

Server-side pseudo-structure:

messages = []  // Array with timestamps or IDs

GET /messages?since=<timestamp>
  → Filter messages newer than timestamp
  → Return matching messages

POST /messages
  → Add message with current timestamp
  → Store in memory (or database)

Client-side pseudo-logic:

lastSeen = 0
every 2 seconds:
  response = GET /messages?since=lastSeen
  if response.messages.length > 0:
    display messages
    lastSeen = latest message timestamp

Add instrumentation to count requests and measure latency between POST and when another client sees it.

Learning milestones:

  1. Server responds to polls → You understand basic HTTP APIs
  2. Messages sync between clients → You understand state synchronization
  3. You measure 95%+ wasted requests → You viscerally understand why polling is bad
  4. You feel the 0-2 second latency → You understand the polling interval tradeoff

Project 2: Long-Polling Server (The First “Push” Hack)

  • File: LEARN_PUSH_NOTIFICATIONS_DEEP_DIVE.md
  • Main Programming Language: Python
  • Alternative Programming Languages: Node.js, Go, Java
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: HTTP / Async Programming / Connection Management
  • Software or Tool: Flask/asyncio or Express.js
  • Main Book: “The Linux Programming Interface” by Michael Kerrisk

What you’ll build: A server that holds HTTP requests open until data arrives. When a client asks “anything new?”, instead of immediately saying “no”, the server waits (up to 30 seconds) for something to happen before responding.

Why it teaches push notifications: Long-polling is the bridge between polling and true push. It shows you how to maintain state across requests, handle timeouts, and deal with the challenges of holding connections open. This technique powered real-time apps before WebSockets.

Core challenges you’ll face:

  • Holding requests without blocking → maps to async programming models
  • Tracking pending connections → maps to stateful connection management
  • Timeout handling → maps to resource cleanup and lifecycle
  • Race conditions → maps to concurrent access to shared state

Key Concepts:

  • Async I/O Models: “The Linux Programming Interface” Chapter 63 - Kerrisk
  • Connection State Management: “TCP/IP Illustrated” Chapter 18 - Stevens
  • HTTP Keep-Alive: RFC 7230 Section 6.3

Difficulty: Intermediate Time estimate: 1 week Prerequisites: Completed Project 1, understanding of async/await or callbacks, basic threading concepts

Real world outcome:

Terminal 1 (Client A):
$ curl "http://localhost:3000/poll?client=A"
[Waiting... connection held open]
[After 5 seconds, message arrives]
{"messages": [{"from": "B", "text": "Hello A!"}]}

Terminal 2 (Client B):
$ curl -X POST http://localhost:3000/send \
  -d '{"to": "A", "text": "Hello A!"}'
{"status": "delivered"}  ← Response comes AFTER client A receives it

Terminal 3 (Server logs):
[12:00:00] Client A connected, holding request...
[12:00:05] Message for A received from B
[12:00:05] Releasing A's pending request with message
[12:00:05] Client A disconnected, new poll expected

Implementation Hints:

The key insight: Instead of responding immediately, you store the response object and respond later.

Conceptual data structures:

pending_connections = {
  "client_A": { response_object, connected_at, timeout_id },
  "client_B": { response_object, connected_at, timeout_id },
}

Pseudo-logic for poll endpoint:

GET /poll?client=<id>
  1. Check if there are already queued messages for this client
     → If yes, return immediately with those messages
  2. If no messages pending:
     → Store this request's response object in pending_connections
     → Set a timeout (30 seconds) to respond with empty array
     → Do NOT send response yet—just return from handler

Pseudo-logic for send endpoint:

POST /send {to, text}
  1. Check if recipient has a pending connection
  2. If yes:
     → Use their stored response object to send the message
     → Remove from pending_connections
     → Clear their timeout
  3. If no:
     → Queue the message for when they next connect

The hardest part: understanding that “returning from the handler” doesn’t mean “sending the response.” In async frameworks, you can hold the response object and use it later.

Learning milestones:

  1. Requests hang for 30 seconds then timeout → You understand holding connections
  2. Messages deliver instantly when sent → You’ve built “push” over HTTP
  3. Multiple clients work simultaneously → You understand concurrent connection handling
  4. No wasted requests → You see the efficiency gain over polling

Project 3: WebSocket Chat Server (True Bidirectional Communication)

  • File: LEARN_PUSH_NOTIFICATIONS_DEEP_DIVE.md
  • Main Programming Language: Node.js
  • Alternative Programming Languages: Go, Rust, Python
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: WebSocket Protocol / Real-time Systems
  • Software or Tool: ws library or native WebSocket
  • Main Book: “The Linux Programming Interface” by Michael Kerrisk

What you’ll build: A real-time chat application using WebSockets. Both server and client can send messages at any time without waiting for a request. You’ll implement the WebSocket handshake, frame parsing, and connection management.

Why it teaches push notifications: WebSockets are the foundation of modern real-time web apps. Understanding how they upgrade from HTTP, maintain persistent connections, and handle the framing protocol gives you the mental model for all push technologies.

Core challenges you’ll face:

  • WebSocket handshake (HTTP Upgrade) → maps to protocol negotiation
  • Frame parsing (opcodes, masking) → maps to binary protocol design
  • Connection lifecycle → maps to handling disconnects, reconnects
  • Broadcasting to multiple clients → maps to pub/sub patterns

Resources for key challenges:

  • RFC 6455 - The WebSocket Protocol

Key Concepts:

  • WebSocket Handshake: RFC 6455 Section 4
  • Frame Format: RFC 6455 Section 5
  • Connection Management: “High Performance Browser Networking” Chapter 17 - Grigorik

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Completed Projects 1-2, understanding of TCP, binary data handling

Real world outcome:

Browser Console (Client A):
> ws = new WebSocket('ws://localhost:8080');
> ws.send(JSON.stringify({type: 'join', room: 'general'}));
> ws.onmessage = (e) => console.log(JSON.parse(e.data));
< {type: 'joined', room: 'general', users: ['A']}
< {type: 'user_joined', user: 'B'}  ← Instant notification!
< {type: 'message', from: 'B', text: 'Hey everyone!'}

Server Terminal:
$ node ws-server.js
WebSocket server on port 8080
[12:00:00] New connection, performing handshake...
[12:00:00] Upgrade: websocket
[12:00:00] Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
[12:00:00] Handshake complete, connection established
[12:00:01] Client A joined room 'general'
[12:00:05] Client B joined room 'general'
[12:00:05] Broadcasting user_joined to room 'general'

Implementation Hints:

WebSocket connection starts as HTTP, then “upgrades”:

Handshake (simplified):

Client sends:
  GET / HTTP/1.1
  Upgrade: websocket
  Connection: Upgrade
  Sec-WebSocket-Key: <base64 random>

Server responds:
  HTTP/1.1 101 Switching Protocols
  Upgrade: websocket
  Connection: Upgrade
  Sec-WebSocket-Accept: <SHA1(key + magic string), base64>

After handshake, communication switches to binary frames:

Frame format:
  [FIN, RSV1-3, Opcode (4 bits)]  ← 1 byte
  [MASK, Payload length (7 bits)] ← 1 byte (+ 0, 2, or 8 extended bytes)
  [Masking key (4 bytes)]         ← Only if MASK=1 (client→server)
  [Payload data]

Key opcodes:

  • 0x1 = Text frame
  • 0x2 = Binary frame
  • 0x8 = Close
  • 0x9 = Ping
  • 0xA = Pong

For learning, implement the handshake and basic text frames yourself before using a library.

Learning milestones:

  1. Handshake succeeds → You understand HTTP upgrade mechanism
  2. You parse a text frame → You understand the WebSocket wire format
  3. Bidirectional messages work → You’ve built true real-time communication
  4. Heartbeat (ping/pong) keeps connections alive → You understand connection health

Project 4: Server-Sent Events (SSE) Notification Stream

  • File: LEARN_PUSH_NOTIFICATIONS_DEEP_DIVE.md
  • Main Programming Language: Go
  • Alternative Programming Languages: Node.js, Python, Rust
  • Coolness Level: Level 2: Practical but Forgettable
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 1: Beginner
  • Knowledge Area: HTTP Streaming / Event-Driven Architecture
  • Software or Tool: Native HTTP Server
  • Main Book: “High Performance Browser Networking” by Ilya Grigorik

What you’ll build: A one-way event stream from server to browser using Server-Sent Events. Unlike WebSockets, SSE only goes server→client, but it’s simpler, auto-reconnects, and works over regular HTTP.

Why it teaches push notifications: SSE shows you that “push” doesn’t always need bidirectional communication. Many notification use cases only need server→client. Understanding when to use SSE vs WebSockets teaches you to pick the right tool.

Core challenges you’ll face:

  • HTTP streaming (chunked transfer) → maps to keeping response open
  • Event format (data, id, retry) → maps to structured streaming protocol
  • Auto-reconnection with Last-Event-ID → maps to resumable streams
  • Multiple event types → maps to event-driven architecture

Key Concepts:

  • SSE Protocol: HTML Living Standard - Server-Sent Events
  • HTTP Chunked Encoding: RFC 7230 Section 4.1
  • Event-Driven Design: “Building Microservices” Chapter 4 - Newman

Difficulty: Beginner Time estimate: Weekend Prerequisites: Basic HTTP understanding, can run a web server

Real world outcome:

Browser (visiting http://localhost:8080/dashboard):
Live Notifications:
• [12:00:05] New order #1234 received
• [12:00:08] Payment confirmed for order #1234
• [12:00:15] Order #1234 shipped
• [12:00:20] User john@example.com signed up

Curl (raw event stream):
$ curl http://localhost:8080/events
event: order
data: {"id": 1234, "status": "received"}
id: 1

event: payment
data: {"order_id": 1234, "amount": 99.99}
id: 2

event: order
data: {"id": 1234, "status": "shipped"}
id: 3

Implementation Hints:

SSE is beautifully simple—it’s just a long-lived HTTP response with a specific format:

Response headers:

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

Event format:

event: <event-type>      ← Optional, defaults to "message"
data: <payload>          ← Can span multiple lines with "data:" prefix
id: <event-id>           ← Optional, for resume support
retry: <milliseconds>    ← Optional, reconnect interval

<blank line>             ← Marks end of event

Server pseudo-logic:

GET /events
  1. Set headers for event stream
  2. Store this connection in active_clients list
  3. Keep connection open
  4. When events happen, format and write to all connections
  5. Handle client disconnect (remove from list)

Client-side (built into browsers):

const es = new EventSource('/events');
es.addEventListener('order', (e) => {
  console.log('Order event:', JSON.parse(e.data));
});
es.addEventListener('error', (e) => {
  console.log('Connection lost, auto-reconnecting...');
});

The killer feature: If connection drops, browser auto-reconnects and sends Last-Event-ID header to resume from where it left off.

Learning milestones:

  1. Browser receives events → You understand HTTP streaming
  2. Multiple event types work → You understand event categorization
  3. Auto-reconnection resumes correctly → You understand event IDs and resumability
  4. You compare with WebSockets → You understand the tradeoffs

Project 5: Service Worker Push Receiver (Browser Push Notifications)

  • File: LEARN_PUSH_NOTIFICATIONS_DEEP_DIVE.md
  • Main Programming Language: JavaScript
  • Alternative Programming Languages: TypeScript
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Service Workers / Web Push API / Browser APIs
  • Software or Tool: Service Worker API
  • Main Book: “High Performance Browser Networking” by Ilya Grigorik

What you’ll build: A web app that can receive push notifications even when the browser tab is closed. You’ll register a Service Worker, subscribe to push, and handle incoming notifications with custom actions.

Why it teaches push notifications: This is where you see the complete browser-side picture. Service Workers run in the background, independent of your web page. Understanding this architecture—and how the browser connects to push services—is essential.

Core challenges you’ll face:

  • Service Worker lifecycle → maps to background process management
  • Push subscription (with VAPID) → maps to key-based authentication
  • Notification permissions → maps to user consent flow
  • Notification actions → maps to interactive notifications

Key Concepts:

  • Service Worker Lifecycle: MDN Web Docs - Service Worker API
  • Push API: W3C Push API Specification
  • Notification API: “Progressive Web Apps” Chapter 8 - Hume

Difficulty: Intermediate Time estimate: 1 week Prerequisites: JavaScript, understanding of async/await, basic understanding of public-key cryptography concepts

Real world outcome:

Browser (with tab closed):
┌─────────────────────────────────────┐
│ 🔔 My App                           │
│ You have a new message from Alice   │
│                                     │
│ [Reply]  [Dismiss]                  │
└─────────────────────────────────────┘

Console (when notification arrives):
Service Worker: Push event received
Service Worker: Payload: {"title": "New Message", "body": "From Alice"}
Service Worker: Showing notification...

Network tab:
← Push message received from wss://updates.push.services.mozilla.com

Implementation Hints:

Service Workers are JavaScript files that run separately from your web page:

Registration flow:

1. Your page: navigator.serviceWorker.register('/sw.js')
2. Browser downloads and installs sw.js
3. sw.js now runs in background, even when tab closes

Push subscription:

1. Your page: registration.pushManager.subscribe({
     userVisibleOnly: true,
     applicationServerKey: <your VAPID public key>
   })
2. Browser contacts its push service (e.g., Mozilla's)
3. Returns subscription object with:
   - endpoint: URL to send push messages to
   - keys: { p256dh, auth } for encryption
4. You send this subscription to your backend

Service Worker push handler:

// In sw.js
self.addEventListener('push', (event) => {
  const data = event.data.json();
  event.waitUntil(
    self.registration.showNotification(data.title, {
      body: data.body,
      actions: [
        { action: 'reply', title: 'Reply' },
        { action: 'dismiss', title: 'Dismiss' }
      ]
    })
  );
});

self.addEventListener('notificationclick', (event) => {
  if (event.action === 'reply') {
    // Open reply window
  }
});

The mental model: Your Service Worker is always listening to the browser’s push service connection, waiting for messages addressed to your app.

Learning milestones:

  1. Service Worker registers and installs → You understand the SW lifecycle
  2. Push subscription succeeds → You understand the permission + registration flow
  3. Notification appears with tab closed → You’ve achieved true “push”
  4. Notification actions work → You understand interactive notifications

Project 6: Web Push Server (Send Encrypted Push Messages)

  • File: LEARN_PUSH_NOTIFICATIONS_DEEP_DIVE.md
  • Main Programming Language: Python
  • Alternative Programming Languages: Node.js, Go, Rust
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Cryptography / HTTP/2 / Push Protocols
  • Software or Tool: Web Push Libraries / OpenSSL
  • Main Book: “Serious Cryptography, 2nd Edition” by Jean-Philippe Aumasson

What you’ll build: A backend server that sends push notifications to browsers. You’ll implement VAPID authentication, message encryption (ECDH + AES-128-GCM), and communicate with push services like Mozilla’s or Google’s.

Why it teaches push notifications: This is where you understand the complete server-side picture. The encryption scheme (RFC 8291) ensures end-to-end privacy—even the push service can’t read your messages. Implementing this yourself reveals why the protocol works the way it does.

Core challenges you’ll face:

  • VAPID JWT signing → maps to application server authentication
  • ECDH key agreement → maps to deriving shared secrets
  • AES-GCM encryption → maps to authenticated encryption
  • HTTP/2 to push services → maps to modern protocol requirements

Resources for key challenges:

Key Concepts:

  • ECDH Key Exchange: “Serious Cryptography” Chapter 11 - Aumasson
  • AES-GCM: “Serious Cryptography” Chapter 8 - Aumasson
  • JWT Signing: RFC 7519

Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Completed Project 5, understanding of public-key cryptography, HTTP/2 basics

Real world outcome:

Terminal (sending a push):
$ python send_push.py \
  --subscription '{"endpoint": "https://updates.push.services.mozilla.com/...",
                   "keys": {"p256dh": "...", "auth": "..."}}' \
  --message '{"title": "Hello!", "body": "From your server"}'

Encrypting message...
  - Generated ephemeral ECDH keypair
  - Derived shared secret via ECDH
  - Computed HKDF-derived encryption key
  - Encrypted payload with AES-128-GCM

Signing VAPID token...
  - Created JWT with exp, sub, aud claims
  - Signed with ES256 (P-256 + SHA-256)

Sending to push service...
  - POST https://updates.push.services.mozilla.com/wpush/v2/...
  - Headers: Authorization: vapid t=<jwt>, k=<public-key>
  - Headers: Content-Encoding: aes128gcm
  - Body: <encrypted payload, 100 bytes>

Response: 201 Created
Push message queued for delivery!

Browser (notification appears):
🔔 Hello!
From your server

Implementation Hints:

The encryption is the hard part. Here’s the conceptual flow:

VAPID (server identification):

1. Generate ECDSA keypair (P-256 curve)
2. Create JWT:
   {
     "aud": "https://push-service.example.com",
     "exp": <12 hours from now>,
     "sub": "mailto:admin@yourapp.com"
   }
3. Sign JWT with your private key
4. Send as: Authorization: vapid t=<jwt>, k=<base64url public key>

Message encryption (RFC 8291):

You have:
  - Subscription's p256dh (user agent public key)
  - Subscription's auth (16 bytes shared auth secret)
  - Your message plaintext

Steps:
1. Generate ephemeral ECDH keypair
2. ECDH: shared_secret = your_private * their_public
3. HKDF to derive:
   - IKM = shared_secret
   - salt = auth
   - info = "WebPush: info" || user_public || server_public
   - Output: PRK
4. HKDF again for content encryption key (CEK) and nonce
5. Pad message to hide length
6. AES-128-GCM encrypt
7. Prepend header: salt(16) || rs(4) || idlen(1) || keyid(65)

For learning, implement encryption step-by-step with lots of debug output. Use a test server to verify your encrypted payloads match expected values.

Learning milestones:

  1. VAPID JWT validates → You understand server authentication
  2. ECDH produces correct shared secret → You understand key agreement
  3. Encrypted message decrypts correctly → You understand the full encryption chain
  4. Push services accept your messages → You’ve built a working push sender

Project 7: Build a Message Queue (The Heart of Push Systems)

  • File: LEARN_PUSH_NOTIFICATIONS_DEEP_DIVE.md
  • Main Programming Language: Go
  • Alternative Programming Languages: Rust, C, Java
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 4. The “Open Core” Infrastructure
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Distributed Systems / Data Structures / Persistence
  • Software or Tool: Building from scratch (inspired by Redis/Kafka)
  • Main Book: “Designing Data-Intensive Applications” by Martin Kleppmann

What you’ll build: A message queue that stores messages for offline clients and delivers them when they reconnect. Supports multiple topics, acknowledgments, and persistence.

Why it teaches push notifications: Every push system needs a message queue. What happens when a device is offline? The message must be stored and delivered later. Understanding queue semantics (at-least-once, at-most-once, exactly-once) is crucial for reliable delivery.

Core challenges you’ll face:

  • Message persistence → maps to durability guarantees
  • Consumer acknowledgments → maps to delivery confirmation
  • Topic/subscription routing → maps to pub/sub patterns
  • Message TTL and expiration → maps to resource management

Key Concepts:

  • Message Queue Semantics: “Designing Data-Intensive Applications” Chapter 11 - Kleppmann
  • Pub/Sub Patterns: “Enterprise Integration Patterns” Chapter 3 - Hohpe & Woolf
  • Write-Ahead Logging: “Designing Data-Intensive Applications” Chapter 3 - Kleppmann

Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Understanding of data structures, file I/O, concurrent programming

Real world outcome:

Terminal 1 (Queue Server):
$ ./message-queue --port 9000 --data-dir ./queue-data
Message Queue Server started on :9000
[12:00:00] Topic 'notifications' created
[12:00:01] Client 'device-123' subscribed to 'notifications'
[12:00:05] Message queued for 'notifications': "Hello!"
[12:00:05] Delivering to device-123... success
[12:00:10] Client 'device-456' subscribed to 'notifications'
[12:00:10] Replaying 1 pending message to device-456
[12:00:15] Message queued: "World!"
[12:00:15] Delivering to device-123... success
[12:00:15] Delivering to device-456... success

Terminal 2 (Producer):
$ ./mq-client publish notifications "Hello!"
Message published, ID: msg-001

$ ./mq-client publish notifications "World!"
Message published, ID: msg-002

Terminal 3 (Consumer):
$ ./mq-client subscribe notifications --client-id device-456
Connecting to queue...
Received: "Hello!" (replayed from queue)
Received: "World!" (live)

Implementation Hints:

Core data structures:

In-memory:

topics = {
  "notifications": {
    messages: [(id, payload, timestamp, ttl), ...],
    subscribers: {
      "device-123": { connection, last_acked_id },
      "device-456": { connection, last_acked_id },
    }
  }
}

Persistence strategy (append-only log):

File: queue-data/notifications.log
[msg-001] [timestamp] [ttl] [payload-length] [payload]
[msg-002] [timestamp] [ttl] [payload-length] [payload]
...

File: queue-data/notifications.acks
[device-123] [last-acked: msg-002]
[device-456] [last-acked: msg-001]

Key operations:

publish(topic, message):
  1. Append to log file
  2. Add to in-memory queue
  3. For each online subscriber:
     → Send message
     → Wait for ACK (or timeout)
  4. For offline subscribers:
     → Message stays in queue

subscribe(topic, client_id, last_acked):
  1. Register connection
  2. Replay all messages since last_acked
  3. Wait for new messages

ack(topic, client_id, message_id):
  1. Update ack file
  2. If all subscribers acked, can garbage collect

Learning milestones:

  1. Pub/sub works in-memory → You understand the routing model
  2. Messages persist across restarts → You understand durability
  3. Offline clients get missed messages → You understand store-and-forward
  4. TTL expires old messages → You understand resource management

Project 8: APNs/FCM Integration Layer

  • File: LEARN_PUSH_NOTIFICATIONS_DEEP_DIVE.md
  • Main Programming Language: Python
  • Alternative Programming Languages: Node.js, Go, Java
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Mobile Push / API Integration / Authentication
  • Software or Tool: APNs HTTP/2 API, FCM HTTP v1 API
  • Main Book: “Building Microservices” by Sam Newman

What you’ll build: A unified push service that sends notifications to both iOS (via APNs) and Android (via FCM) devices. Handle device registration, authentication (JWT for APNs, OAuth for FCM), and delivery feedback.

Why it teaches push notifications: Real applications target multiple platforms. Understanding how APNs and FCM differ—authentication, payload formats, feedback mechanisms—prepares you for production mobile push.

Core challenges you’ll face:

  • APNs JWT authentication → maps to token-based auth with Apple
  • FCM OAuth2 service accounts → maps to Google Cloud authentication
  • Platform-specific payloads → maps to abstraction layer design
  • Handling feedback (invalid tokens) → maps to device lifecycle

Key Concepts:

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: HTTP/2 knowledge, understanding of OAuth2, mobile development basics helpful

Real world outcome:

Terminal (unified push sender):
$ python unified_push.py \
  --platform ios \
  --token "abc123..." \
  --title "New Message" \
  --body "You have a new message from Alice"

Sending to APNs...
  - Target: api.push.apple.com:443
  - Auth: Bearer <JWT signed with APNs key>
  - Payload: {"aps": {"alert": {...}, "badge": 1}}
  - Response: 200 OK, apns-id: 123-456-789

$ python unified_push.py \
  --platform android \
  --token "def456..." \
  --title "New Message" \
  --body "You have a new message from Alice"

Sending to FCM...
  - Target: fcm.googleapis.com/v1/projects/myapp/messages:send
  - Auth: Bearer <OAuth2 access token>
  - Payload: {"message": {"token": "...", "notification": {...}}}
  - Response: 200 OK, name: projects/myapp/messages/xyz

$ python unified_push.py \
  --platform both \
  --user-id user-123 \
  --title "New Message" \
  --body "You have a new message from Alice"

Looking up devices for user-123...
  - iOS: 2 devices
  - Android: 1 device
Sending 3 notifications...
  ✓ iOS device 1: delivered
  ✗ iOS device 2: invalid token (removing from database)
  ✓ Android device 1: delivered

Implementation Hints:

APNs authentication (JWT):

Header:
  { "alg": "ES256", "kid": "<your key ID>" }

Payload:
  {
    "iss": "<your team ID>",
    "iat": <current timestamp>
  }

Sign with your APNs Auth Key (.p8 file)
Use as: Authorization: bearer <jwt>

APNs request:

POST /3/device/<device-token> HTTP/2
host: api.push.apple.com
apns-topic: com.yourapp.bundle
authorization: bearer <jwt>
content-type: application/json

{
  "aps": {
    "alert": {
      "title": "New Message",
      "body": "From Alice"
    },
    "badge": 1,
    "sound": "default"
  }
}

FCM authentication (OAuth2):

1. Load service account JSON
2. Create JWT with Google OAuth claims
3. Exchange JWT for access token via Google's OAuth endpoint
4. Use as: Authorization: Bearer <access_token>

FCM request:

POST /v1/projects/<project-id>/messages:send HTTP/1.1
Host: fcm.googleapis.com
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "message": {
    "token": "<device-registration-token>",
    "notification": {
      "title": "New Message",
      "body": "From Alice"
    }
  }
}

Learning milestones:

  1. APNs accepts your notifications → You understand Apple’s auth model
  2. FCM accepts your notifications → You understand Google’s auth model
  3. Unified interface works for both → You’ve built an abstraction layer
  4. Invalid token feedback handled → You understand device lifecycle

Project 9: Connection Multiplexer (How One Connection Serves Many Apps)

  • File: LEARN_PUSH_NOTIFICATIONS_DEEP_DIVE.md
  • Main Programming Language: Rust
  • Alternative Programming Languages: Go, C, C++
  • Coolness Level: Level 5: Pure Magic
  • Business Potential: 4. The “Open Core” Infrastructure
  • Difficulty: Level 4: Expert
  • Knowledge Area: Systems Programming / Networking / Protocol Design
  • Software or Tool: tokio/mio (async I/O)
  • Main Book: “The Linux Programming Interface” by Michael Kerrisk

What you’ll build: A daemon that maintains a single connection to an upstream server and multiplexes messages for multiple local applications. This is how APNs works on iOS—one system connection serves all apps.

Why it teaches push notifications: This is the architectural secret of mobile push. Understanding multiplexing reveals why push services are centralized, how battery life is optimized, and how OS-level push daemons work.

Core challenges you’ll face:

  • Connection pooling and keepalive → maps to resource sharing
  • Message routing to correct app → maps to demultiplexing
  • App registration and lifecycle → maps to IPC with local apps
  • Graceful reconnection → maps to connection resilience

Key Concepts:

  • Async I/O: “The Linux Programming Interface” Chapter 63 - Kerrisk
  • IPC Mechanisms: “The Linux Programming Interface” Chapter 43-46 - Kerrisk
  • Connection Multiplexing: HTTP/2 Streams (RFC 7540)

Difficulty: Expert Time estimate: 3-4 weeks Prerequisites: Systems programming experience, async I/O understanding, IPC mechanisms

Real world outcome:

Terminal 1 (Multiplexer Daemon):
$ sudo ./push-mux --upstream wss://push.example.com --socket /var/run/pushmux.sock
Push Multiplexer started
  - Upstream: wss://push.example.com
  - Local socket: /var/run/pushmux.sock
[12:00:00] Connected to upstream
[12:00:01] App 'com.example.chat' registered (PID 1234)
[12:00:02] App 'com.example.mail' registered (PID 5678)
[12:00:05] Upstream message for 'com.example.chat': {...}
[12:00:05] Delivered to PID 1234 via socket
[12:00:10] Upstream message for 'com.example.mail': {...}
[12:00:10] Delivered to PID 5678 via socket
[12:00:15] App 'com.example.chat' unregistered (PID 1234 exited)
[12:00:20] Connection lost, reconnecting...
[12:00:21] Reconnected to upstream

Terminal 2 (App using the multiplexer):
$ ./my-app
Registering with push multiplexer...
Registered for app ID: com.example.chat
Waiting for notifications...
Received: {"type": "message", "from": "alice", "text": "Hello!"}

Implementation Hints:

Architecture:

┌─────────────┐      ┌─────────────────┐      ┌──────────────┐
│ Upstream    │◄────►│ Push Multiplexer│◄────►│ App A (IPC)  │
│ Push Server │      │ (Single Conn)   │◄────►│ App B (IPC)  │
└─────────────┘      └─────────────────┘◄────►│ App C (IPC)  │
                                              └──────────────┘

Key data structures:

upstream_connection: WebSocket or TCP
registered_apps: {
  "com.example.chat": {
    socket: UnixSocket,
    pid: 1234,
    registered_at: timestamp
  },
  "com.example.mail": { ... }
}

Upstream protocol (simplified):

Register apps with upstream:
  {"type": "register", "apps": ["com.example.chat", "com.example.mail"]}

Incoming messages tagged with target:
  {"type": "push", "app": "com.example.chat", "payload": {...}}

Local IPC protocol (Unix sockets):

App → Daemon:
  {"type": "register", "app_id": "com.example.chat"}
  {"type": "unregister"}

Daemon → App:
  {"type": "registered", "status": "ok"}
  {"type": "notification", "payload": {...}}

Challenges: Handle app crashes (detect closed socket), upstream reconnection (re-register all apps), and multiple messages in flight.

Learning milestones:

  1. Single upstream connection established → You understand connection sharing
  2. Local apps register via IPC → You understand Unix domain sockets
  3. Messages route to correct app → You understand demultiplexing
  4. Survives upstream disconnect → You understand resilience patterns

Project 10: End-to-End Encrypted Push System

  • File: LEARN_PUSH_NOTIFICATIONS_DEEP_DIVE.md
  • Main Programming Language: Go
  • Alternative Programming Languages: Rust, Python, TypeScript
  • Coolness Level: Level 5: Pure Magic
  • Business Potential: 4. The “Open Core” Infrastructure
  • Difficulty: Level 4: Expert
  • Knowledge Area: Cryptography / Security / Protocol Design
  • Software or Tool: libsodium or native crypto libraries
  • Main Book: “Serious Cryptography, 2nd Edition” by Jean-Philippe Aumasson

What you’ll build: A complete push notification system where the server (and any intermediary) cannot read message content. Only the sender and recipient can decrypt messages, using public-key cryptography.

Why it teaches push notifications: Privacy-preserving push is the gold standard. Understanding E2E encryption—key exchange, forward secrecy, and the challenges of key management—prepares you for building truly secure notification systems.

Core challenges you’ll face:

  • Key exchange without prior contact → maps to X3DH or similar protocols
  • Forward secrecy → maps to ephemeral keys per message
  • Key management → maps to storing and rotating keys
  • Metadata protection → maps to what encryption doesn’t hide

Resources for key challenges:

Key Concepts:

  • Elliptic Curve Cryptography: “Serious Cryptography” Chapter 11 - Aumasson
  • Key Exchange Protocols: “Serious Cryptography” Chapter 12 - Aumasson
  • Authenticated Encryption: “Serious Cryptography” Chapter 8 - Aumasson

Difficulty: Expert Time estimate: 3-4 weeks Prerequisites: Strong understanding of public-key cryptography, completed Projects 6-7

Real world outcome:

Terminal 1 (Alice - generates identity):
$ ./e2e-push keygen --user alice
Generated identity keypair for 'alice'
  Public key: A1B2C3D4...
  (Private key stored securely)

Upload your public key to the server:
$ ./e2e-push register --user alice --server push.example.com
Registered with server. Devices can now send you encrypted messages.

Terminal 2 (Bob - sends encrypted message to Alice):
$ ./e2e-push send \
  --to alice \
  --message "Secret meeting at 3pm"

Fetching Alice's public key from server...
Generating ephemeral keypair for this message...
Deriving shared secret via X25519...
Encrypting with XChaCha20-Poly1305...
Sending encrypted payload to server...

Message sent! Server received:
  encrypted_payload: [72 bytes of ciphertext]
  ephemeral_public: [32 bytes]
  (Server cannot decrypt this)

Terminal 1 (Alice receives):
$ ./e2e-push receive
New encrypted message received!
Decrypting with my private key...
From: bob
Message: "Secret meeting at 3pm"

Server logs show only:
[12:00:00] Encrypted message: alice <- [72 bytes] (cannot read)

Implementation Hints:

Key hierarchy:

Identity keypair (long-term):
  - Generated once per user
  - Public key shared with server
  - Private key never leaves device

Ephemeral keypair (per-message):
  - Generated fresh for each message
  - Provides forward secrecy
  - Public key sent with message

Encryption flow:

Sender has: recipient_public_key, message
Sender does:
  1. Generate ephemeral keypair (eph_private, eph_public)
  2. shared_secret = X25519(eph_private, recipient_public_key)
  3. encryption_key = HKDF(shared_secret, salt, info)
  4. ciphertext = XChaCha20-Poly1305(encryption_key, nonce, message)
  5. Send: { eph_public, nonce, ciphertext }

Decryption flow:

Recipient has: my_private_key, message
Recipient does:
  1. shared_secret = X25519(my_private_key, eph_public)
  2. encryption_key = HKDF(shared_secret, salt, info)  # Same as sender
  3. message = XChaCha20-Poly1305_Decrypt(encryption_key, nonce, ciphertext)

Why forward secrecy: If Alice’s long-term private key is compromised later, past messages are still safe because each used a unique ephemeral key that was deleted after use.

Learning milestones:

  1. Key exchange produces same shared secret on both sides → You understand ECDH
  2. Messages encrypt and decrypt correctly → You understand authenticated encryption
  3. Server demonstrably can’t read messages → You understand E2E encryption
  4. Ephemeral keys provide forward secrecy → You understand advanced key management

Project 11: Push Notification Load Tester

  • File: LEARN_PUSH_NOTIFICATIONS_DEEP_DIVE.md
  • Main Programming Language: Go
  • Alternative Programming Languages: Rust, Python (with asyncio)
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Performance Testing / Concurrent Programming / Metrics
  • Software or Tool: Native concurrency primitives
  • Main Book: “Systems Performance” by Brendan Gregg

What you’ll build: A tool that simulates thousands of connected clients and measures push notification latency, throughput, and reliability under load.

Why it teaches push notifications: Understanding performance characteristics reveals system bottlenecks. How many concurrent connections can your server handle? What’s the 99th percentile latency? This project teaches you to answer these questions.

Core challenges you’ll face:

  • Simulating many concurrent connections → maps to connection pooling and limits
  • Measuring latency accurately → maps to clock synchronization
  • Generating realistic load patterns → maps to traffic modeling
  • Collecting and analyzing metrics → maps to observability

Key Concepts:

  • Benchmarking Methodology: “Systems Performance” Chapter 2 - Gregg
  • Concurrency Patterns: “Go in Practice” Chapter 3 - Butcher & Farina
  • Metrics Collection: “Site Reliability Engineering” Chapter 6 - Google

Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Understanding of concurrent programming, completed Projects 3 or 4

Real world outcome:

$ ./push-loadtest \
  --server ws://localhost:8080 \
  --clients 10000 \
  --messages-per-second 1000 \
  --duration 60s

Push Notification Load Test
============================
Server: ws://localhost:8080
Simulated clients: 10,000
Target throughput: 1,000 msg/s
Duration: 60s

Connecting clients...
  [████████████████████] 10,000/10,000 connected (12.3s)

Running test...
  [████████████████████] 60s elapsed

Results
=======
Total messages sent: 60,000
Messages delivered: 59,847
Messages lost: 153 (0.26%)

Latency (ms):
  p50:  12.3
  p90:  45.7
  p99:  189.2
  p99.9: 512.8
  max:  1,234.5

Throughput:
  Actual: 997.5 msg/s
  Peak:   1,247.3 msg/s

Connection stats:
  Reconnections: 23
  Connection errors: 7
  Avg connection lifetime: 58.2s

Server CPU: 78% (estimated from response times)

Implementation Hints:

Client simulation:

For each simulated client:
  1. Connect via WebSocket
  2. Register unique client ID
  3. Log timestamp when message received
  4. Report metrics to central collector

Message sending:

Producer goroutines:
  1. Pick random connected client
  2. Record send_timestamp
  3. Send message via server
  4. When client receives, calculate latency = receive_time - send_time

Metrics collection:

Maintain:
  - Histogram of latencies (for percentiles)
  - Counter of sent/received/lost messages
  - Gauge of active connections
  - Rate calculator for throughput

Challenges:

  • Clock sync: If sender and receiver are different machines, use message IDs and match send/receive times
  • Connection limits: OS limits (ulimit), port exhaustion
  • Memory: 10K connections = 10K goroutines + 10K buffers

Learning milestones:

  1. 1,000 simultaneous connections work → You understand basic concurrency
  2. Latency percentiles are accurate → You understand performance measurement
  3. You find the breaking point → You understand system limits
  4. You identify bottlenecks → You understand performance debugging

Project 12: Build Your Own Push Service (Mini-APNs)

  • File: LEARN_PUSH_NOTIFICATIONS_DEEP_DIVE.md
  • Main Programming Language: Rust
  • Alternative Programming Languages: Go, C++
  • Coolness Level: Level 5: Pure Magic
  • Business Potential: 5. The “Industry Disruptor”
  • Difficulty: Level 5: Master
  • Knowledge Area: Distributed Systems / Protocol Design / Infrastructure
  • Software or Tool: Building from scratch
  • Main Book: “Designing Data-Intensive Applications” by Martin Kleppmann

What you’ll build: A complete push notification service similar to APNs or FCM: device registration, authentication, message queueing, delivery, feedback, and an SDK for apps to integrate.

Why it teaches push notifications: This is the capstone. You’ll design every component, make every tradeoff, and understand why real push services work the way they do.

Core challenges you’ll face:

  • Device token generation and validation → maps to identity management
  • Authentication (app credentials) → maps to multi-tenant security
  • Message routing and delivery → maps to distributed message passing
  • Feedback service (invalid tokens, delivery status) → maps to async feedback loops
  • High availability → maps to distributed systems design

Key Concepts:

  • Distributed System Design: “Designing Data-Intensive Applications” Chapter 8 - Kleppmann
  • Message Delivery Guarantees: “Designing Data-Intensive Applications” Chapter 11 - Kleppmann
  • Authentication at Scale: “Building Microservices” Chapter 9 - Newman

Difficulty: Master Time estimate: 1-2 months Prerequisites: All previous projects, strong systems programming background

Real world outcome:

Your Push Service: YourPush (like APNs/FCM but yours!)

1. Developer creates an app:
   $ yourpush create-app "My Chat App"
   App created!
   App ID: app_abc123
   API Key: key_xyz789
   SDK download: https://yourpush.example.com/sdk

2. App registers device:
   // In mobile app using your SDK
   YourPush.register(appId: "app_abc123") { token in
     print("Device token: \(token)")
     // Send token to app backend
   }

   // Your service generates: tok_device_9f8e7d6c...

3. App server sends notification:
   $ curl -X POST https://api.yourpush.example.com/v1/push \
     -H "Authorization: Bearer key_xyz789" \
     -d '{
       "token": "tok_device_9f8e7d6c...",
       "notification": {
         "title": "New Message",
         "body": "Hello from YourPush!"
       }
     }'

   {"id": "msg_123", "status": "queued"}

4. User's device receives notification:
   ┌─────────────────────────────────────┐
   │ 🔔 My Chat App                      │
   │ New Message                         │
   │ Hello from YourPush!                │
   └─────────────────────────────────────┘

5. Feedback API:
   $ curl https://api.yourpush.example.com/v1/feedback \
     -H "Authorization: Bearer key_xyz789"

   {
     "invalid_tokens": ["tok_device_old123..."],
     "delivery_status": [
       {"id": "msg_123", "status": "delivered", "at": "..."}
     ]
   }

Implementation Hints:

System architecture:

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   API        │────►│  Message     │────►│  Delivery    │
│   Gateway    │     │  Queue       │     │  Workers     │
└──────────────┘     └──────────────┘     └──────────────┘
       │                    │                    │
       ▼                    ▼                    ▼
┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│   Auth       │     │  Postgres    │     │  Connection  │
│   Service    │     │  (metadata)  │     │  Brokers     │
└──────────────┘     └──────────────┘     └──────────────┘
                                                 │
                                                 ▼
                                          ┌──────────────┐
                                          │   Devices    │
                                          │   (mobile)   │
                                          └──────────────┘

Device token design:

Token format: tok_<app_id>_<random_bytes>_<signature>
  - Encodes which app the token belongs to
  - Random bytes for uniqueness
  - Signature to prevent forgery

Message flow:

1. API receives push request
2. Validate API key → find app
3. Validate token → find device and connection broker
4. Queue message with TTL
5. Delivery worker picks up message
6. Find device's active connection (or queue for later)
7. Send message through connection
8. Record delivery status
9. If device offline, store for later (with TTL)

Feedback service:

Track:
  - Invalid tokens (device unregistered)
  - Delivery successes/failures
  - Last activity per device

Expose via API so app servers can:
  - Remove invalid tokens from their database
  - Track message delivery rates

Learning milestones:

  1. Device registration works → You understand token management
  2. Messages deliver to online devices → You understand real-time routing
  3. Messages queue for offline devices → You understand store-and-forward
  4. Feedback API shows delivery status → You understand the full lifecycle
  5. Multiple apps work in isolation → You understand multi-tenancy

Project 13: Push Analytics Dashboard

  • File: LEARN_PUSH_NOTIFICATIONS_DEEP_DIVE.md
  • Main Programming Language: TypeScript (React + Node.js)
  • Alternative Programming Languages: Python (Django), Go (with HTMX)
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Data Visualization / Analytics / Full-Stack
  • Software or Tool: React, D3.js or Chart.js, WebSockets
  • Main Book: “Designing Data-Intensive Applications” by Martin Kleppmann

What you’ll build: A real-time dashboard showing push notification metrics: delivery rates, latency percentiles, failures by device type, and trends over time.

Why it teaches push notifications: Operating a push system requires observability. Understanding what to measure—and how to visualize it—teaches you what matters in production.

Core challenges you’ll face:

  • Real-time metric streaming → maps to live data updates
  • Time-series aggregation → maps to efficient data storage
  • Identifying anomalies → maps to alerting and monitoring
  • Drill-down by dimensions → maps to OLAP concepts

Key Concepts:

  • Time-Series Data: “Designing Data-Intensive Applications” Chapter 3 - Kleppmann
  • Real-Time Dashboards: “Streaming Systems” Chapter 1 - Akidau et al.
  • Visualization Principles: “The Visual Display of Quantitative Information” - Tufte

Difficulty: Intermediate Time estimate: 2 weeks Prerequisites: Frontend development experience, basic data analysis

Real world outcome:

Browser (http://localhost:3000/dashboard):

┌────────────────────────────────────────────────────────────────┐
│  Push Notification Dashboard                        🟢 Live   │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  Today's Stats                                                 │
│  ┌──────────────┐ ┌──────────────┐ ┌──────────────┐           │
│  │  1,234,567   │ │    98.7%     │ │    23ms      │           │
│  │  Delivered   │ │  Success     │ │  p50 Latency │           │
│  └──────────────┘ └──────────────┘ └──────────────┘           │
│                                                                │
│  Delivery Rate (last 24 hours)                                 │
│  ╔════════════════════════════════════════════════════════╗   │
│  ║     ▂▃▄▅▆▇▆▅▄▃▂▃▄▅▆▇▆▅▄▃▂▃▄▅▆▇▆▅▄▃▂▃▄▅             ║   │
│  ╚════════════════════════════════════════════════════════╝   │
│    12am   4am    8am   12pm   4pm    8pm   now                 │
│                                                                │
│  Failures by Reason                    By Platform             │
│  ┌─────────────────────────┐    ┌─────────────────────────┐   │
│  │ Invalid Token   45%    │    │ 🍎 iOS      62%        │   │
│  │ Expired         32%    │    │ 🤖 Android  35%        │   │
│  │ Rate Limited    18%    │    │ 🌐 Web      3%         │   │
│  │ Other            5%    │    └─────────────────────────┘   │
│  └─────────────────────────┘                                   │
│                                                                │
└────────────────────────────────────────────────────────────────┘

Implementation Hints:

Metrics to track:

Per message:
  - send_time, deliver_time, status
  - platform (ios/android/web)
  - app_id

Aggregations:
  - Count by status per minute/hour/day
  - Latency percentiles (p50, p90, p99)
  - Success rate by platform
  - Failure reasons distribution

Backend data pipeline:

1. Push service emits events:
   {"type": "delivered", "latency_ms": 45, "platform": "ios", ...}

2. Stream processor (or simple in-memory):
   - Aggregate into time buckets
   - Calculate running percentiles
   - Update counters

3. WebSocket endpoint streams live updates to dashboard

Frontend approach:

1. Connect WebSocket to backend
2. Receive metric updates every second
3. Update charts with smooth transitions
4. Allow time range selection (1h, 24h, 7d)
5. Click-to-drill-down (e.g., click "iOS" to see iOS-only view)

Learning milestones:

  1. Live metrics stream to dashboard → You understand real-time data flow
  2. Charts update smoothly → You understand incremental rendering
  3. You identify an issue from the dashboard → You understand observability value
  4. Drill-down reveals root cause → You understand dimensional analysis

Project 14: Rate Limiter and Abuse Prevention

  • File: LEARN_PUSH_NOTIFICATIONS_DEEP_DIVE.md
  • Main Programming Language: Go
  • Alternative Programming Languages: Rust, Python
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Security / Algorithms / Distributed Systems
  • Software or Tool: Redis (optional), Native implementations
  • Main Book: “Designing Data-Intensive Applications” by Martin Kleppmann

What you’ll build: A rate limiting system that prevents abuse of your push service: per-app limits, per-device limits, burst handling, and distributed rate limiting across multiple servers.

Why it teaches push notifications: Real push services must handle abuse. Understanding rate limiting algorithms—token bucket, leaky bucket, sliding window—and how to implement them in a distributed setting is essential for production systems.

Core challenges you’ll face:

  • Rate limiting algorithms → maps to token bucket, sliding window
  • Distributed state → maps to consistency vs availability tradeoff
  • Fair queuing → maps to preventing one client from starving others
  • Abuse detection → maps to anomaly detection

Key Concepts:

  • Rate Limiting Algorithms: “System Design Interview” Chapter 4 - Xu
  • Distributed Counting: “Designing Data-Intensive Applications” Chapter 12 - Kleppmann
  • Token Bucket: Classic algorithm (RFC 2697)

Difficulty: Advanced Time estimate: 2 weeks Prerequisites: Understanding of distributed systems, completed Project 7 or 12

Real world outcome:

Terminal (rate limiter in action):

$ ./push-ratelimit --config limits.yaml

Config loaded:
  Default: 100 msg/min per app
  Burst: 20 messages
  Per-device: 10 msg/min

[12:00:00] app_123: 15 messages → 15 allowed (burst)
[12:00:01] app_123: 100 messages → 85 allowed, 15 rate-limited
[12:00:02] app_123: 50 messages → 0 allowed, quota exhausted
[12:00:02] Returning 429 Too Many Requests
           X-RateLimit-Limit: 100
           X-RateLimit-Remaining: 0
           X-RateLimit-Reset: 58 (seconds)

[12:00:05] app_456: 10 messages → 10 allowed (different quota)

[12:01:00] app_123: quota reset
[12:01:00] app_123: 50 messages → 50 allowed

Abuse detection:
[12:05:00] ⚠️  app_789 sending to same device 500 times
[12:05:00] Device rate limit triggered: tok_device_xyz
[12:05:00] ⚠️  Pattern detected: notification spam
[12:05:00] App throttled to 10 msg/min (suspicious activity)

Implementation Hints:

Token bucket algorithm:

For each app/device:
  bucket = {
    tokens: current token count (starts at burst_size)
    last_update: timestamp
  }

On request:
  1. Refill tokens based on time elapsed:
     tokens_to_add = (now - last_update) * (rate / 60)
     tokens = min(tokens + tokens_to_add, burst_size)
     last_update = now

  2. Try to consume a token:
     if tokens >= 1:
       tokens -= 1
       return ALLOW
     else:
       return DENY (429 Too Many Requests)

Sliding window log:

For each app/device:
  requests = [timestamp1, timestamp2, ...]

On request:
  1. Remove timestamps older than window (e.g., 1 minute)
  2. If len(requests) < limit:
     Add current timestamp
     return ALLOW
  3. Else:
     return DENY

Distributed rate limiting:

Challenge: Multiple servers, one rate limit

Options:
  1. Centralized (Redis):
     INCR app:123:requests
     EXPIRE app:123:requests 60

  2. Local + eventual sync:
     Each server tracks locally
     Periodically sync to central store
     Accept some over-limit requests

  3. Token bucket with Redis:
     MULTI
       GET app:123:tokens
       SET app:123:tokens <new_value>
       SET app:123:last_update <now>
     EXEC

Abuse patterns to detect:

  • Same device targeted repeatedly
  • Sending to many invalid tokens
  • Sudden traffic spike from quiet app
  • Large payloads consistently

Learning milestones:

  1. Basic rate limiting works → You understand token bucket
  2. Burst handling is correct → You understand the difference between rate and burst
  3. Distributed rate limiting works → You understand distributed state challenges
  4. Abuse patterns are detected → You understand security monitoring

Project 15: Cross-Platform Push SDK

  • File: LEARN_PUSH_NOTIFICATIONS_DEEP_DIVE.md
  • Main Programming Language: Swift + Kotlin + TypeScript
  • Alternative Programming Languages: React Native (JS), Flutter (Dart)
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 4. The “Open Core” Infrastructure
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Mobile Development / SDK Design / Cross-Platform
  • Software or Tool: Xcode, Android Studio, npm
  • Main Book: “Building Mobile Apps at Scale” by Gergely Orosz

What you’ll build: Client SDKs for iOS, Android, and Web that integrate with your push service (from Project 12). Handle device registration, token refresh, notification display, and deep linking.

Why it teaches push notifications: Push services are only as good as their client integration. Understanding how to build SDKs that handle the complexity of multiple platforms, OS versions, and edge cases is essential.

Core challenges you’ll face:

  • Platform-specific APIs → maps to abstraction across iOS/Android/Web
  • Token lifecycle management → maps to token refresh, migration
  • Background execution → maps to OS-specific constraints
  • Rich notifications → maps to images, actions, custom UI

Key Concepts:

  • iOS Push Architecture: Apple Developer Documentation - UserNotifications
  • Android Push Architecture: Firebase Cloud Messaging documentation
  • SDK Design: “Building Mobile Apps at Scale” Chapter 12 - Orosz

Difficulty: Advanced Time estimate: 3-4 weeks Prerequisites: Mobile development experience, completed Project 12

Real world outcome:

iOS (Swift):
import YourPush

@main
class AppDelegate: UIApplicationDelegate {
  func application(_ application: UIApplication,
                   didFinishLaunching...) {
    YourPush.configure(appId: "app_123", apiKey: "key_xyz")

    YourPush.onNotification { notification in
      print("Received: \(notification.title)")
    }

    YourPush.register { result in
      switch result {
        case .success(let token):
          print("Token: \(token)")
        case .failure(let error):
          print("Registration failed: \(error)")
      }
    }
  }
}

Android (Kotlin):
class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    YourPush.configure(this, "app_123", "key_xyz")

    YourPush.register { result ->
      result.onSuccess { token ->
        Log.d("YourPush", "Token: $token")
      }
      result.onFailure { error ->
        Log.e("YourPush", "Failed: ${error.message}")
      }
    }
  }
}

Web (TypeScript):
import { YourPush } from 'yourpush-web';

await YourPush.configure({
  appId: 'app_123',
  apiKey: 'key_xyz',
  serviceWorkerPath: '/sw.js'
});

const token = await YourPush.register();
console.log('Device token:', token);

YourPush.onMessage((notification) => {
  console.log('Received:', notification);
});

Implementation Hints:

iOS architecture:

YourPush (main class):
  - configure(appId, apiKey)
  - register() → requests permission, registers with APNs, exchanges for your token
  - onNotification(handler)

Internal:
  - UNUserNotificationCenter for permissions
  - UIApplication.registerForRemoteNotifications()
  - Send APNs token to your server, get back your token
  - Handle token refresh when APNs token changes

Android architecture:

YourPush (main class):
  - configure(context, appId, apiKey)
  - register() → gets FCM token, exchanges for your token
  - onNotification(handler)

Internal:
  - FirebaseMessagingService subclass
  - Override onNewToken() for token refresh
  - Override onMessageReceived() for foreground messages
  - NotificationManager for displaying notifications

Web architecture:

YourPush:
  - configure(options)
  - register() → requests permission, subscribes to push, sends to server
  - onMessage(handler)

Internal:
  - navigator.serviceWorker.register()
  - PushManager.subscribe() with VAPID key
  - Service Worker handles push events when page closed

Consistent API across platforms:

Every SDK should have:
  - configure(appId, apiKey)
  - register() → Promise<Token>
  - unregister() → Promise<void>
  - onNotification(handler)
  - getToken() → Token | null
  - isRegistered() → bool

Learning milestones:

  1. iOS SDK registers and receives → You understand iOS push internals
  2. Android SDK registers and receives → You understand FCM integration
  3. Web SDK registers and receives → You understand Service Workers
  4. Consistent API across all three → You understand SDK design

Project Comparison Table

# Project Difficulty Time Depth Fun Factor Business Value
1 HTTP Polling Chat Beginner Weekend ★★☆☆☆ ★★☆☆☆ Resume Gold
2 Long-Polling Server Intermediate 1 week ★★★☆☆ ★★★☆☆ Resume Gold
3 WebSocket Chat Intermediate 1-2 weeks ★★★★☆ ★★★★☆ Micro-SaaS
4 Server-Sent Events Beginner Weekend ★★★☆☆ ★★★☆☆ Micro-SaaS
5 Service Worker Push Intermediate 1 week ★★★★☆ ★★★★☆ Micro-SaaS
6 Web Push Server Advanced 2-3 weeks ★★★★★ ★★★★☆ Service/Support
7 Message Queue Advanced 2-3 weeks ★★★★★ ★★★★☆ Open Core
8 APNs/FCM Integration Intermediate 1-2 weeks ★★★★☆ ★★★☆☆ Service/Support
9 Connection Multiplexer Expert 3-4 weeks ★★★★★ ★★★★★ Open Core
10 E2E Encrypted Push Expert 3-4 weeks ★★★★★ ★★★★★ Open Core
11 Load Tester Advanced 2 weeks ★★★★☆ ★★★☆☆ Service/Support
12 Build Push Service Master 1-2 months ★★★★★ ★★★★★ Disruptor
13 Analytics Dashboard Intermediate 2 weeks ★★★☆☆ ★★★★☆ Micro-SaaS
14 Rate Limiter Advanced 2 weeks ★★★★☆ ★★★☆☆ Service/Support
15 Cross-Platform SDK Advanced 3-4 weeks ★★★★★ ★★★★☆ Open Core

If you’re a beginner:

Start with Project 1 (Polling)Project 2 (Long-Polling)Project 3 (WebSockets). This sequence teaches you the evolution of real-time communication and gives you the foundation for everything else.

If you want practical, production-ready skills quickly:

Go Project 5 (Service Worker Push)Project 6 (Web Push Server)Project 8 (APNs/FCM Integration). You’ll be able to send push notifications to browsers and mobile devices within a few weeks.

If you want deep systems understanding:

Follow the full path: Projects 1-3 (foundations) → Project 7 (message queue) → Project 9 (multiplexer) → Project 12 (build your own push service). This is the path to truly understanding how APNs and FCM work internally.

If you’re interested in security:

Focus on Project 6 (Web Push Encryption)Project 10 (E2E Encrypted Push)Project 14 (Rate Limiting). You’ll understand the cryptographic foundations and abuse prevention.


Final Capstone Project: Production-Grade Push Notification Platform

File: LEARN_PUSH_NOTIFICATIONS_DEEP_DIVE.md Main Programming Language: Go (backend), TypeScript (frontend), Swift/Kotlin (mobile SDKs) Alternative Programming Languages: Rust (backend), React Native (unified mobile) Coolness Level: Level 5: Pure Magic Business Potential: 5. The “Industry Disruptor” Difficulty: Level 5: Master Knowledge Area: Distributed Systems / Full-Stack / Platform Engineering Software or Tool: Kubernetes, PostgreSQL, Redis, gRPC Main Book: “Designing Data-Intensive Applications” by Martin Kleppmann

What you’ll build: A complete, production-grade push notification platform that could compete with OneSignal or Pusher Beams. Multi-tenant, horizontally scalable, with SDKs for all platforms, an admin dashboard, and comprehensive monitoring.

Why this is the ultimate project: This combines everything: the networking from Projects 1-4, the encryption from Projects 6 and 10, the message queuing from Project 7, the multi-platform delivery from Project 8, the multiplexing from Project 9, the core service from Project 12, the analytics from Project 13, the security from Project 14, and the SDKs from Project 15.

Core challenges you’ll face:

  • Multi-tenancy at scale → maps to isolation, quotas, billing
  • Horizontal scaling → maps to stateless services, consistent hashing
  • High availability → maps to redundancy, failover, graceful degradation
  • Developer experience → maps to documentation, SDKs, onboarding
  • Operations → maps to deployment, monitoring, incident response

Real world outcome:

YourPush Platform (https://yourpush.example.com)

┌──────────────────────────────────────────────────────────────────┐
│                                                                  │
│  "Push Notifications Made Simple"                                │
│                                                                  │
│  ✓ 10 million+ messages/day capacity                            │
│  ✓ iOS, Android, Web SDKs                                       │
│  ✓ 99.9% uptime SLA                                             │
│  ✓ <100ms p99 latency                                           │
│  ✓ End-to-end encryption                                        │
│  ✓ Real-time analytics dashboard                                │
│                                                                  │
│  [Get Started Free] [Documentation] [Pricing]                    │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

Developer Dashboard:
┌──────────────────────────────────────────────────────────────────┐
│  My Apps > Chat App                                              │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  API Keys         Today's Stats         Quick Send               │
│  ┌────────────┐   ┌────────────┐        ┌────────────────────┐  │
│  │ key_xyz... │   │ 45,678     │        │ Device: [________] │  │
│  │ [Regenerate]   │ Delivered  │        │ Title:  [________] │  │
│  └────────────┘   └────────────┘        │ Body:   [________] │  │
│                                         │ [Send Test]        │  │
│  SDK Integration                        └────────────────────┘  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │ npm install @yourpush/web                                  │ │
│  │ pod 'YourPush'                                             │ │
│  │ implementation 'com.yourpush:sdk:1.0.0'                    │ │
│  └────────────────────────────────────────────────────────────┘ │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

Infrastructure (what you've built):
┌─────────────────────────────────────────────────────────────────────┐
│                           Load Balancer                             │
│                                │                                    │
│         ┌──────────────────────┼──────────────────────┐            │
│         ▼                      ▼                      ▼            │
│   ┌───────────┐          ┌───────────┐          ┌───────────┐      │
│   │ API       │          │ API       │          │ API       │      │
│   │ Server 1  │          │ Server 2  │          │ Server 3  │      │
│   └─────┬─────┘          └─────┬─────┘          └─────┬─────┘      │
│         │                      │                      │            │
│         └──────────────────────┼──────────────────────┘            │
│                                ▼                                    │
│                        ┌───────────────┐                           │
│                        │ Message Queue │                           │
│                        │ (Kafka/Redis) │                           │
│                        └───────┬───────┘                           │
│                                │                                    │
│         ┌──────────────────────┼──────────────────────┐            │
│         ▼                      ▼                      ▼            │
│   ┌───────────┐          ┌───────────┐          ┌───────────┐      │
│   │ Delivery  │          │ Delivery  │          │ Delivery  │      │
│   │ Worker 1  │          │ Worker 2  │          │ Worker 3  │      │
│   └─────┬─────┘          └─────┬─────┘          └─────┬─────┘      │
│         │                      │                      │            │
│         ▼                      ▼                      ▼            │
│   ┌───────────┐          ┌───────────┐          ┌───────────┐      │
│   │ APNs      │          │ FCM       │          │ Web Push  │      │
│   └───────────┘          └───────────┘          └───────────┘      │
│                                                                     │
│   ┌───────────────────────────────────────────────────────────┐    │
│   │                    PostgreSQL (metadata)                   │    │
│   │                    Redis (rate limiting, caching)          │    │
│   │                    InfluxDB (metrics)                      │    │
│   │                    Grafana (monitoring)                    │    │
│   └───────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────────┘

Implementation Hints:

This is too large to detail fully, but here’s the architecture:

Services to build:

1. API Gateway: REST/gRPC, authentication, rate limiting
2. Device Registry: Store device tokens, manage lifecycle
3. Message Router: Determine delivery path for each message
4. Delivery Workers: Send to APNs/FCM/Web Push
5. Feedback Processor: Handle delivery receipts, invalid tokens
6. Analytics Engine: Aggregate metrics, power dashboard
7. Admin Dashboard: React app for developers
8. SDKs: iOS, Android, Web client libraries

Key design decisions:

- Stateless API servers (scale horizontally)
- Message queue for async delivery (handle bursts)
- Consistent hashing for device → worker routing
- Redis for rate limiting and caching
- PostgreSQL for durable metadata
- gRPC for internal service communication
- Kubernetes for orchestration

MVP scope:

Phase 1: Single-server, Web Push only
Phase 2: Add APNs and FCM
Phase 3: Horizontal scaling
Phase 4: Multi-tenancy and billing
Phase 5: Advanced features (segments, A/B testing)

Learning milestones:

  1. MVP works end-to-end → You can send push notifications via your platform
  2. Multiple tenants isolated → You understand multi-tenancy
  3. Handles 1K msg/sec → You understand performance engineering
  4. Survives server failure → You understand high availability
  5. Developers can onboard themselves → You’ve built a product

Summary

# Project Name Main Language
1 HTTP Polling Chat JavaScript (Node.js)
2 Long-Polling Server Python
3 WebSocket Chat Server Node.js
4 Server-Sent Events Go
5 Service Worker Push Receiver JavaScript
6 Web Push Server Python
7 Build a Message Queue Go
8 APNs/FCM Integration Layer Python
9 Connection Multiplexer Rust
10 End-to-End Encrypted Push System Go
11 Push Notification Load Tester Go
12 Build Your Own Push Service Rust
13 Push Analytics Dashboard TypeScript
14 Rate Limiter and Abuse Prevention Go
15 Cross-Platform Push SDK Swift + Kotlin + TypeScript
Capstone Production-Grade Push Platform Go + TypeScript + Swift/Kotlin

Key Resources

RFCs and Standards

Platform Documentation

Books

  • “Designing Data-Intensive Applications” by Martin Kleppmann - For distributed systems fundamentals
  • “TCP/IP Illustrated, Volume 1” by W. Richard Stevens - For network protocol understanding
  • “Serious Cryptography” by Jean-Philippe Aumasson - For encryption implementation
  • “The Linux Programming Interface” by Michael Kerrisk - For systems programming
  • “High Performance Browser Networking” by Ilya Grigorik - For WebSocket and SSE understanding

Articles and Tutorials


This learning path will take you from understanding why we need push notifications to building a production-grade push notification platform. By the end, you’ll deeply understand every layer of the stack—from the TCP connection to the cryptographic envelope to the distributed message queue.