The job board from Level 1 is live. Now the CEO wants applicants to know about new job postings instantly and receive an email when they apply. You have one week. No double emails allowed.
Add a simple email send using Resend SDK. Understand why sending email synchronously in the request handler is wrong.
Implement idempotency keys + SSE + Outbox Pattern. Know the difference between at-least-once and exactly-once delivery. Add DLQ monitoring.
Design the full async architecture. Add DLQ monitoring with alerts, retry policies, back-pressure for SSE reconnect storms. Explain the Redis fallback strategy.
The job board from Level 1 is live. Now the CEO wants applicants to know about new job postings instantly and receive an email when they apply. You have one week. No double emails allowed.
The most common upgrade to a working product: make it real-time and add notifications. This is where most junior engineers introduce two production bugs: duplicate email sends (because the API is called twice on retry) and race conditions between the SSE connection and the initial data load. Both bugs are invisible in development. Both appear immediately in production. This challenge is that upgrade — made safe.
The question this raises
How do you guarantee exactly-once email delivery when users retry failed requests?
A user submits a job application. The network drops after the server saves to the DB but before the browser gets the response. The browser shows "Network Error." The user clicks Apply again. There is no idempotency key and no unique constraint on (user_id, job_id). What happens?
Lesson outline
How this concept changes your thinking
User applies for a job — network drops mid-request
“Frontend sends POST /api/apply. Network hiccup. Response lost. Frontend shows "Network Error." User clicks Apply again. Two applications submitted. Two confirmation emails sent. Two entries in the DB. Recruiter sees the same candidate applied twice.”
“Idempotency key: frontend generates a UUID on component mount. Includes it as Idempotency-Key header on every request. Server checks Redis: has this key been processed? Yes -> return cached response. No -> process and cache result for 24 hours. One email regardless of how many retries.”
New job posted — applicants should see it instantly
“Frontend polls GET /api/jobs every 10 seconds. 1,000 users = 6,000 requests/minute at idle. Postgres CPU at 30% just from polling. Every poll hits the DB even when nothing changed.”
“Server-Sent Events: client opens GET /api/jobs/stream. Server pushes new job events as they happen. 1,000 users = 1,000 persistent connections, zero DB polling. Redis pub/sub: when admin posts a job, PUBLISH jobs-channel newJob. SSE workers subscribed to the channel push to all connected clients.”
Email confirmation added to the apply flow
“POST /api/apply: save to DB, send email via Resend (synchronous). Resend API times out (5s). Request fails. DB has the application. Email was not sent. User gets error but application IS saved. User retries. Duplicate application.”
“POST /api/apply: save application + save outbox event in ONE transaction. Return 201 immediately (50ms). Background BullMQ worker reads outbox, sends email, marks sent. If email fails: worker retries 3 times. User always gets 201. Email always arrives eventually.”
3 bugs every recruiter finds in a "real-time + email" feature
1. Double email: retry with no idempotency key sends the same email twice. Fix: Idempotency-Key header + Redis dedup cache. 2. Missing job update: SSE connection opens after the job was published — client misses the event. Fix: send current state on SSE connect, then stream deltas. 3. Email success but application not saved: email sent before DB transaction commits. Fix: Outbox Pattern — DB write first, email after commit.
The browser console shows a duplicate POST request on retry. The DB has two application rows. Redis has the idempotency key from the first request — but nobody checked it. The SSE stream shows a connection opened 3 seconds after the job was published. Reading all three reveals: no idempotency, no initial state on SSE connect.
1// GET /api/jobs/stream — SSE endpoint with Redis pub/sub2import { redis } from '@/lib/redis';3import { db } from '@/lib/db';45export async function GET(req: Request) {6const encoder = new TextEncoder();78const stream = new ReadableStream({9async start(controller) {10const send = (data: object) => {11controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}1213`));14};1516// CRITICAL: Send current state on connect (prevents missed events)17// Client connecting 3s after a job was posted would miss it otherwise18const existingJobs = await db.job.findMany({19orderBy: { createdAt: 'desc' },20take: 20,21});22send({ type: 'initial', jobs: existingJobs });2324// Subscribe to Redis pub/sub for new job events25const subscriber = redis.duplicate();26await subscriber.subscribe('jobs-channel', (message) => {27const job = JSON.parse(message);28send({ type: 'new-job', job }); // push to all connected clients29});3031// Cleanup on disconnect32req.signal.addEventListener('abort', async () => {33await subscriber.unsubscribe('jobs-channel');34await subscriber.quit();35controller.close();36});37},38});3940return new Response(stream, {41headers: {42'Content-Type': 'text/event-stream',43'Cache-Control': 'no-cache',44'Connection': 'keep-alive',45},46});47}4849// Idempotency key middleware50export async function checkIdempotency(req: Request) {51const idempotencyKey = req.headers.get('Idempotency-Key');52if (!idempotencyKey) return null; // key not provided — no dedup5354const cached = await redis.get(`idem:${idempotencyKey}`);55if (cached) {56// Already processed — return the cached response57return Response.json(JSON.parse(cached), { status: 200 });58}59return null; // not yet processed — proceed normally60}6162export async function storeIdempotencyResult(key: string, result: object) {63// Cache the result for 24 hours64await redis.setex(`idem:${key}`, 86400, JSON.stringify(result));65}
1// Outbox Pattern + BullMQ email worker2import { db } from '@/lib/db';3import { Queue, Worker } from 'bullmq';4import { Resend } from 'resend';5import { redis } from '@/lib/redis';67const emailQueue = new Queue('emails', { connection: redis });8const resend = new Resend(process.env.RESEND_API_KEY);910// POST /api/apply — atomic DB write + outbox event11export async function submitApplication(data: ApplicationInput) {12const idempotencyKey = data.idempotencyKey;1314const application = await db.$transaction(async (tx) => {15// Save application16const app = await tx.application.create({17data: { jobId: data.jobId, email: data.email, resumeUrl: data.resumeUrl },18});1920// Outbox event in the SAME transaction (atomic)21await tx.outboxEvent.create({22data: {23id: idempotencyKey, // use idempotency key as outbox event ID24topic: 'email.confirmation',25payload: JSON.stringify({ applicationId: app.id, email: data.email }),26processedAt: null,27},28});2930return app;31});3233return { applicationId: application.id };34}3536// Outbox relay: reads committed events, enqueues to BullMQ37async function runOutboxRelay() {38const events = await db.outboxEvent.findMany({39where: { processedAt: null },40take: 50,41});4243for (const event of events) {44await emailQueue.add(event.topic, JSON.parse(event.payload), {45jobId: event.id, // BullMQ dedup: same jobId = same job (not re-added)46attempts: 3,47backoff: { type: 'exponential', delay: 2000 },48});49await db.outboxEvent.update({50where: { id: event.id },51data: { processedAt: new Date() },52});53}54}5556// Email worker: idempotent send via Resend57const emailWorker = new Worker('emails', async (job) => {58const { applicationId, email } = job.data;5960await resend.emails.send({61from: 'jobs@company.com',62to: email,63subject: 'Application received',64html: `<p>Application ${applicationId} received.</p>`,65headers: {66'X-Idempotency-Key': applicationId, // Resend deduplicates on their side too67},68});69}, {70connection: redis,71concurrency: 5,72});7374emailWorker.on('failed', (job, err) => {75console.error(`[DLQ] Email job ${job?.id} failed after all retries: ${err.message}`);76// Alert on-call if DLQ has > 0 jobs77});
4 Features of the Upgrade
Stripe requires an idempotency key on every payment API call. Their documentation explicitly states: "Generate the idempotency key on the client before the request. Never generate it on the server." This is because if the request fails, the client must retry with the same key — which is only possible if the client generated and stored it before the first attempt.
The interview question: "What if Redis goes down?"
Prepared answer: if Redis is down, the idempotency cache is unavailable. Fallback: check the DB for an existing application (same user_id + job_id = duplicate). Slower (DB query vs Redis get) but safe. The Redis cache is a performance optimization, not the only protection against duplicates. This shows defense in depth: Redis first, DB constraint as fallback.
1// Complete production apply flow with idempotency2'use client';3import { useRef } from 'react';45// Generate idempotency key once on component mount6// NOT on button click — retries must use the same key7export function ApplyButton({ jobId }: { jobId: string }) {8const idempotencyKey = useRef(crypto.randomUUID());910async function handleApply() {11const res = await fetch('/api/apply', {12method: 'POST',13headers: {14'Content-Type': 'application/json',15'Idempotency-Key': idempotencyKey.current, // same key on every retry16},17body: JSON.stringify({ jobId }),18});1920if (!res.ok && res.status !== 200) {21// User can retry — same idempotency key prevents duplicate22console.error('Apply failed, safe to retry');23}24}2526return <button onClick={handleApply}>Apply</button>;27}2829// Server: POST /api/apply30export async function POST(req: Request) {31const idempotencyKey = req.headers.get('Idempotency-Key');3233// Check idempotency cache first34if (idempotencyKey) {35const cached = await redis.get(`idem:${idempotencyKey}`);36if (cached) return Response.json(JSON.parse(cached)); // return cached result37}3839const { jobId } = await req.json();40const session = await auth();41if (!session) return Response.json({ error: 'Unauthorized' }, { status: 401 });4243// Atomic: application + outbox event in one transaction44const result = await db.$transaction(async (tx) => {45const app = await tx.application.create({46data: { jobId, userId: session.user.id },47});48await tx.outboxEvent.create({49data: {50id: idempotencyKey ?? crypto.randomUUID(),51topic: 'email.confirmation',52payload: JSON.stringify({ applicationId: app.id, email: session.user.email }),53processedAt: null,54},55});56return { applicationId: app.id };57});5859// Cache idempotency result for 24h60if (idempotencyKey) {61await redis.setex(`idem:${idempotencyKey}`, 86400, JSON.stringify(result));62}6364return Response.json(result, { status: 201 });65}
SSE vs WebSockets
📖 What the exam expects
SSE is one-way (server to client). WebSockets are bidirectional.
Toggle between what certifications teach and what production actually requires
Build challenges at the mid level test whether you understand async reliability patterns — not just whether the feature works in the happy path.
Common questions:
Strong answers include:
Red flags:
Ready to see how this works in the cloud?
Switch to Career Paths for structured paths (e.g. Developer, DevOps) and provider-specific lessons.
View role-based pathsSign in to track your progress and mark lessons complete.
Questions? Discuss in the community or start a thread below.
Join DiscordSign in to start or join a thread.