Oliver White
3 May 2026 · 7 min read
AI Art Arena runs on Vercel. Vercel is serverless — there's no persistent Node.js process staying alive between requests. This creates a problem: contests need to archive automatically when their end date passes, and a new contest needs to open immediately after. These are time-based operations that can't run inside a web request.
Vercel does support cron jobs — you can configure a function to run on a schedule in vercel.json. But cron functions are fire-and-forget. They run, they finish, and if they fail, they fail silently. There's no built-in retry, no event chaining, no state persistence across steps, and no way to trigger one job from the output of another.
| Requirement | Simple cron | Inngest |
|---|---|---|
| Automatic retries on failure | No | Yes — configurable backoff |
| Chain jobs (A triggers B) | No | Yes — event-driven |
| Step-level observability | No | Yes — per-step logs in dashboard |
| Long-running (>10s) support | No | Yes — steps can each take minutes |
| Local development | Awkward | npx inngest-cli dev — full local runtime |
| Type-safe event payloads | No | Yes — TypeScript throughout |
The automation is an event chain, not a monolithic cron. Each function does one thing and fires an event that triggers the next:
Never instantiate Resend at the module top level in an Inngest function. Inngest imports all function files at startup to register them — if Resend is constructed at import time and RESEND_API_KEY isn't set in that environment, the import fails and all functions break. Always create the client inside the handler.
Contest duration, voting cooldown, and reminder timing all come from the system_config table — not from hardcoded constants. This means changing the contest cycle from 7 days to 14 days requires one SQL update, not a code deploy:
Built with this methodology
Vercel cron functions fire and forget — no retries, no event chaining, no step-level observability. Inngest adds all three. Here is how the contest automation chain works: archive fires an event, that event triggers create-next-contest, with automatic retries throughout.
From the build log