Transforming API responses from seconds to milliseconds with durable execution
·7 min read
"Abstract illustration of light-speed data transfer through digital prism with orange, blue, and purple color spectrum representing high-performance network architecture"
Series Background: This is Part 3 of the Portfolio series. . Following the initial build and security hardening, here we explore how event-driven architecture transforms user experience and system reliability using Inngest for background job processing.
Your API routes are lying to your users. They return 200 OK while work is still happening. The contact form says “Message sent!” but the email hasn’t been delivered yet.
Event-driven architecture fixes this by separating acknowledgment from processing. Users get instant feedback. Work happens reliably in the background. Here’s how makes this practical for any project.
When I first built this portfolio’s contact form, the flow was straightforward but slow:
Typescript
// The old way: synchronous processingexport async function POST(request: NextRequest) { const { name, email, message } = await request.json(); // User waits 1-2 seconds for this... await resend.emails.send({ from: FROM_EMAIL, to: AUTHOR_EMAIL, subject: `Contact form: ${name}`, text: message, }); // Only then do they see success return NextResponse.json({ success: true });}
This approach has real problems:
Slow responses: Users wait 1-2 seconds for external API calls
Fragile: If Resend is slow or down, the entire request fails
No retries: Network blip means the email is lost forever
Poor UX: Spinners spinning while users wonder if it worked
The fix isn’t making the email faster—it’s decoupling the response from the work.
Processing the work (async, can be slow, can retry)
Text
Before (Synchronous):User → API Route → Email Service → Response └─────── 1-2 seconds ──────┘After (Event-Driven):User → API Route → Queue Event → Response (< 100ms) ↓ Background Function → Email Service └─── Retries if needed ───┘
The insight: users don’t care when the email sends—they care that you acknowledged their message.
Takeaway
Event-driven architecture isn’t about making work faster—it’s about decoupling the response from
the work. Users get instant feedback. Processing happens reliably in the background with
automatic retries and observability.
For Leaders
Event-driven architecture transforms API reliability and user experience. This post demonstrates real production results: 10x faster response times, 99.9% delivery reliability, and automated security monitoring—all without managing queue infrastructure.
Modern event-driven systems add one more concept: steps. Instead of one monolithic background function, you break work into discrete, named operations. Each step becomes a checkpoint—if step 3 fails, steps 1 and 2 don’t re-run. This is called , and it transforms how you think about reliability.
Implement event-driven architecture in any Next.js project with Inngest. Use step functions for durable execution, automatic retries with exponential backoff, and a local dev UI for debugging. Zero infrastructure required.
// src/app/api/contact/route.tsimport { inngest } from '@/inngest/client';export async function POST(request: NextRequest) { const { name, email, message } = await request.json(); // Validate and sanitize inputs... // Queue the event (returns immediately) await inngest.send({ name: 'contact/form.submitted', data: {
The API route now completes in under 100ms. The user sees instant feedback.
Takeaway
Follow the Validate → Queue → Respond pattern: validate and sanitize inputs synchronously,
queue the event for background processing, and return an instant response. Never expose background
processing failures to users.
Each step.run() creates a checkpoint. If Step 2 fails:
Step 1 doesn’t re-run (notification already sent)
Only Step 2 retries
You see exactly where it failed in the Inngest dashboard
This is —your function survives failures and resumes from the last successful step.
Takeaway
Each step.run() call creates a durable checkpoint. If step 3 fails, steps 1 and 2 don’t re-run.
This means emails aren’t sent twice, analytics aren’t double-counted, and retries are surgical
rather than wasteful.
This runs three times daily, checks for CVEs affecting React/Next.js/RSC packages, verifies against my actual installed versions, and alerts me before I read about it on Twitter.
After migrating to event-driven architecture (measured in this portfolio’s production environment):
Metric
Before
After
Contact form response time
1–2s (observed)
Under 100ms (observed)
Email delivery reliability
~95% (observed)
99.9% (with 3 retries, based on Inngest’s exponential backoff)
GitHub data freshness
On-demand
Pre-cached hourly
Failed job visibility
None
Full dashboard
Security advisory detection
Manual
Automated (3x daily)
Note: These metrics reflect this specific implementation. Your results may vary based on network conditions, third-party API performance, and deployment region.
More importantly: users notice. The contact form feels instant. The contribution heatmap loads immediately. Security issues get flagged before they become problems.
Decouple acknowledgment from processing—users want fast feedback, not fast completion
Steps create checkpoints—failed steps retry without re-running successful ones
Scheduled tasks benefit too—pre-populate caches, monitor systems, aggregate data
Local dev matters—Inngest’s dev UI makes debugging enjoyable
Start simple—you don’t need event-driven for everything
Event-driven architecture doesn’t require enterprise scale. With tools like Inngest, any Next.js project can benefit from reliable background processing, instant API responses, and better observability.
Background jobs are a security surface. This post covers automated CVE monitoring (3x daily GHSA checks), input validation in async pipelines, and how a 13-hour detection gap led to building automated security advisory monitoring.
Event-driven architecture isn’t always the answer:
Simple CRUD: Reading/writing to a database doesn’t need queuing. Saving a user preference? Just write to the database directly.
Real-time requirements: If users need the result immediately, don’t defer it. A “Reply to comment” feature where users expect to see their comment appear instantly should stay synchronous—even if it takes 500ms.
Debugging complexity: More moving parts means more places to look. If your team is already stretched thin, the observability benefits might not outweigh the learning curve.
Small scale: If you have 10 users/day, synchronous is simpler. Don’t add infrastructure complexity you don’t need yet.
The rule of thumb: if users don’t need to wait for the result, don’t make them wait. But if they do need to see the result immediately, don’t hide it behind a queue.
Takeaway
Event-driven architecture isn’t always the answer. Simple CRUD, real-time user feedback, and
low-traffic apps don’t need queuing. Add complexity only when users are waiting for work they
don’t need to see complete.
One more thing: background jobs are a security surface too. The security monitor in this portfolio exists because I learned the hard way that CVE detection gaps matter. If you’re processing sensitive data in background functions, apply the same security rigor you would do to API routes—validate inputs, sanitize outputs, and monitor for anomalies.
The contact form still says “Message sent!”—but now it’s actually true (or will be, with retries, within seconds).
When a post hits 1,000 views, I get notified. The trending calculation runs hourly, scoring posts by recent activity to surface what readers are finding valuable.