Serverless Scheduling: Beyond Platform Cron

Platform cron handles fixed recurring tasks. Production apps need dynamic scheduling — per-user timers, event-driven deadlines, retries. Posthook is the scheduling layer that works with any serverless platform.

Last updated: March 24, 2026

Serverless platforms handle “something happens now, respond to it” well. They do not handle “remember to do something later” well. When you deploy to Vercel, Netlify, Cloudflare Workers, or AWS Lambda, you get request handling, edge routing, and automatic scaling — but you lose the ability to schedule work for a specific future time.

Platform cron features partially fill this gap. Vercel Cron Jobs, Cloudflare Cron Triggers, and Netlify Scheduled Functions all support fixed recurring schedules defined at deploy time. But most production scheduling is dynamic — triggered by user actions, with variable future times, needing retries and visibility when things fail. “Send this user a reminder in 24 hours” is one API call with a scheduling service and fundamentally impossible with platform cron.

What platform cron can do

Every major serverless platform offers some form of cron:

  • Vercel Cron Jobs — cron expressions in vercel.json. 100 jobs per project. Daily precision on Hobby, per-minute on Pro. No retry on failure. UTC only. Logs retained 1 hour (Hobby) or 1 day (Pro).
  • Cloudflare Cron Triggers — cron expressions in wrangler.toml. 250 triggers on Paid plan. Per-minute precision. No retry. 30-second execution limit.
  • Netlify Scheduled Functions — cron expressions in function config. Per-minute precision. Background functions get 15-minute execution limit. No retry. No execution history UI.
  • AWS EventBridge Scheduler — the most capable option. Cron or one-time schedules via API. Up to 10M schedules per region. Configurable retries. Timezone-aware. But requires IAM roles, SDK integration, and deep AWS infrastructure knowledge.
CapabilityPlatform cron (Vercel, Cloudflare, Netlify)AWS EventBridge Scheduler
Dynamic schedulingNo — requires redeploymentYes — API-driven, supports one-time
Retry on failureNoneConfigurable
Timezone supportUTC onlyUTC and timezone-aware
Delivery visibilityEphemeral function logsCloudWatch Logs
InfrastructureZero — included with platformIAM roles, SDK integration, AWS account

Platform cron is the right tool for what it was designed for: a handful of fixed recurring tasks with zero configuration cost. Daily database cleanup, hourly cache warming, periodic data sync — these fit the model cleanly.

AWS EventBridge Scheduler stands apart as the most capable option. It supports one-time schedules created via API, configurable retries, and up to 10 million schedules per region. But it requires an AWS account, IAM roles, SDK integration, and deep infrastructure knowledge — which is often the complexity serverless developers chose their platform to avoid.

The dynamic scheduling gap

The limitation is not that platform cron is unreliable. The limitation is that most scheduling work in a production application is not fixed-cadence. It is event-driven.

“Send this user a reminder in 24 hours.” Each user signs up at a different time. You need a timer per signup event, not a single recurring schedule. Platform cron cannot express this.

“Expire this trial 14 days after activation.” Each trial starts on a different date. Platform cron has no concept of per-entity deadlines. The common workaround is a cron job that scans a database every minute — a workaround with its own failure modes.

“Retry a failed payment next Tuesday at 10am in the user’s timezone.” This requires scheduling a one-time future delivery, in a specific timezone, with DST handling. Not expressible with any platform cron feature except EventBridge.

Cancel or reschedule a pending timer. The appointment was moved — cancel the old reminder and schedule a new one. Platform cron has no API for dynamic schedule management.

Know when a scheduled task failed. Platform cron has no retry mechanism and no alerting. If a cron invocation fails, the failure sits in ephemeral logs until it ages out.

These are not edge cases. They are the core scheduling problems in SaaS applications: reminders, expirations, follow-ups, retries, and notifications — each tied to a specific user, order, or event.

The polling workaround

The most common workaround for dynamic scheduling on serverless is a cron job that polls a database:

  1. Store scheduled tasks in a table with a run_at timestamp
  2. Set up a cron job that runs every minute
  3. Query for tasks where run_at <= NOW() and process them

This works at low volume. At production scale, it becomes its own operational surface:

  • Database load. Every poll cycle runs a query, even when nothing is due. Add SELECT ... FOR UPDATE SKIP LOCKED for concurrency safety, and contention grows with volume.
  • Timing imprecision. Poll every minute and tasks can fire up to 59 seconds late. Poll every 10 seconds and database load increases 6x.
  • Concurrency issues. Multiple serverless function instances polling simultaneously can pick up the same work. Preventing duplicates requires distributed locking — more infrastructure to maintain.
  • Connection exhaustion. Each serverless invocation opens a new database connection. Without connection pooling (PgBouncer, Neon Pooler), the database runs out of connections under load.
  • Missed windows. If the cron trigger fails — cold start, platform issue, function timeout — an entire interval of work is missed with no retry.
  • Cost at idle. A Vercel Pro cron running every minute is 43,200 function invocations per month, even when there is nothing to process.

The polling pattern is not wrong — it is a scheduling system. And maintaining a scheduling system is the infrastructure that serverless was supposed to eliminate.

How Posthook fills the gap

Posthook is a managed scheduling layer. When an event happens in your application — a user signs up, a trial activates, an order is placed — you schedule a hook via API. Posthook persists the schedule, delivers it at the right time via HTTP POST or WebSocket, retries on failure, and tracks every delivery attempt.

No database polling. No cron scanning for work. Each event creates its own timer.

import Posthook from "@posthook/node";

const posthook = new Posthook("phk_...");

// When a user signs up, schedule a reminder for 48 hours later
await posthook.hooks.schedule({
  path: "/api/hooks/onboarding-reminder",
  postIn: "48h",
  data: { userId: "user_abc", step: "complete-profile" },
});

// When a trial activates, schedule expiration at a specific time
await posthook.hooks.schedule({
  path: "/api/hooks/expire-trial",
  postAt: "2026-04-07T00:00:00Z",
  data: { trialId: "trial_xyz" },
});

// Schedule in the user's local timezone (DST-safe)
await posthook.hooks.schedule({
  path: "/api/hooks/payment-retry",
  postAtLocal: "2026-03-31T10:00:00",
  timezone: "America/New_York",
  data: { paymentId: "pay_123" },
});

When the hook fires, your handler checks state and decides what to do:

// app/api/hooks/onboarding-reminder/route.ts
export async function POST(req: Request) {
  const { data } = await req.json();
  const user = await db.users.findById(data.userId);

  // Handler checks state — act or skip
  if (!user || user.profileCompleted) {
    return Response.json({ skipped: true });
  }

  await sendOnboardingReminder(user);
  return Response.json({ sent: true });
}

This works on Vercel, Netlify, Cloudflare Workers, AWS Lambda — any platform with an HTTP endpoint. The handler is a standard serverless function. No new runtime, no SDK lock-in on execution. To see these patterns running end-to-end, try the live Next.js demo or clone the starter repo.

Key capabilities:

  • Three scheduling modespostAt for exact UTC timestamps, postAtLocal + timezone for DST-safe local scheduling, postIn for relative delays
  • Retries with configurable backoff — fixed, exponential, or jitter, with per-hook overrides at scheduling time
  • Per-delivery observability — inspect each hook’s attempt history, status code, response body, and error details from the dashboard
  • Anomaly detection — per-endpoint failure rate tracking against historical baselines, with alerts via email, Slack, or webhook
  • Incident response — bulk retry, cancel, or replay failed hooks filtered by time range, endpoint, or sequence
  • Sequences for recurring work — calendar scheduling with DST handling, dependency graphs, and config-as-code via posthook.toml

Three categories of serverless scheduling solutions

If you need dynamic scheduling on a serverless platform, the options fall into three categories:

Workflow execution platforms

Inngest, Trigger.dev, Temporal. These are full execution engines. You write functions using their SDK, and your code runs in their infrastructure (or self-hosted infrastructure for Temporal). They handle scheduling, retries, branching, multi-step orchestration, and durable state.

The right tool when the problem is genuinely a workflow — multi-step processes with branching logic, long-running operations, and complex state machines. Broader scope, steeper adoption curve. See Inngest / Trigger.dev vs Posthook and Temporal vs Posthook.

Scheduling and delivery services

Posthook, QStash. API call in, HTTP delivery out. Your code runs in your infrastructure. The service handles persistence, timing, delivery, and retries.

The right tool when the problem is “schedule an HTTP call for later” with delivery guarantees, retries, and observability. Simpler integration model — no new runtime, no SDK abstraction over your execution. See QStash vs Posthook.

DIY infrastructure

Cron + database polling, Redis queues (BullMQ), custom queue workers. Maximum control over every aspect of scheduling and execution. You own the infrastructure, the retry logic, the monitoring, and the operational burden.

The right tool when you have specific infrastructure requirements, an existing ops team, or workloads that do not fit managed service pricing. The tradeoff is engineering time building and maintaining scheduling infrastructure instead of building product. See Cron vs durable scheduling and BullMQ vs Posthook.

When platform cron is enough

Platform cron is the right choice when:

  • The schedule is static and known at deploy time — daily cleanup, hourly sync, periodic cache warming
  • You have a small number of recurring tasks (not per-user or per-event timers)
  • You do not need retries, delivery tracking, or failure alerting
  • UTC-only timing is acceptable
  • You want zero additional configuration, billing, or external dependencies

If the entire scheduling need is “run this database cleanup every night,” a cron expression in vercel.json or wrangler.toml is the simplest and most direct solution. There is no reason to add another service for that.

The decision point is when scheduling becomes dynamic. The first time a user action needs to create its own timer — a reminder, an expiration, a follow-up — platform cron cannot express the problem, and the polling workaround starts accumulating operational cost.

Frequently asked questions

Ready to get started?

Create your free account and start scheduling hooks in minutes. No credit card required.