Sapior LogoSapior

Serverless Bill Shock: Track Edge Function and Database Expirations Before They Cost You

A practical guide to monitoring expiration signals across Vercel, Supabase, Netlify, and Neon—so a forgotten webhook or idle database never becomes a surprise invoice.

Serverless pricing changed the failure mode: instead of a server you own, you rent per invocation, per GB-hour, per compute session. The bill does not usually come from the app that is working. It comes from the resource that did not expire.

A cron job you forgot on Vercel. A Supabase project that paused and then resumed under a different plan. A Neon database that should have scaled to zero but stayed warm because of a connection pooler. A Netlify preview that kept accepting edge requests. Each one is an expiration event you should have tracked.

Why serverless costs surprise teams

Traditional monitoring watches errors and latency. Cost monitoring often stops at a total budget alert. That tells you *after* the overage has already happened.

Serverless billing is based on short-lived resources:

Edge functions bill by requests and CPU/GB-hours.

Databases bill by compute time, storage, and egress.

Platforms expire or reset limits monthly, and some pause free projects after inactivity.

The useful signal is not just 'spend is up.' It is 'this resource will exceed its limit in 3 days' or 'this database will pause in 12 hours.'

> Serverless bill shock is almost always a lifecycle problem: a resource lives longer or runs more often than expected.

What to track: expiration signals, not just totals

For each provider, track two types of expirations:

1. **Billing period resets** — monthly quota windows for invocations, bandwidth, build minutes, egress, and compute hours.

2. **Resource state expirations** — free project pauses, scale-to-zero thresholds, preview deployment retention, and spend caps.

A simple model:

provider, resource_type, resource_id, limit_type, observed_value, limit_value, expires_at, state

You want a cron job that reads this model every few hours and alerts when `expires_at - now < 7 days` or when `observed_value / limit_value > 0.8`.

Provider-by-provider signals

Vercel

Vercel bills on function invocations, function duration, edge requests, edge middleware invocations, bandwidth, and web analytics. The danger zone is a function invoked by a forgotten webhook or a retry loop.

Track these Vercel signals:

`function_invocations`

`function_duration_gb_hrs`

`edge_requests`

`edge_middleware_invocations`

`bandwidth_gb`

`spend_cap` or plan limit

Use the [Vercel usage docs](https://vercel.com/docs/accounts/plans/usage) to map plan limits to your project's actual consumption. Vercel's dashboard shows usage, but a scheduled read prevents the end-of-month surprise.

Supabase

Supabase free projects [pause after 1 week of inactivity](https://supabase.com/docs/guides/platform/free-plan). Paid projects do not pause, but compute, storage, and egress still reset monthly.

Track:

`project_status` (active, paused, restoring)

`db_size_gb`

`storage_size_gb`

`egress_gb`

`monthly_reset_at`

If a project pauses, downstream requests fail. If you auto-restore without checking usage, you may create a new billing cycle or hit overages.

Netlify

Netlify's bill shock often comes from bandwidth and edge function invocations on high-traffic previews or old sites. Build minutes are another common overage.

Track:

`edge_function_invocations`

`bandwidth_gb`

`build_minutes`

`preview_deploy_expires_at`

`monthly_reset_at`

Check the [Netlify billing usage docs](https://docs.netlify.com/accounts-and-billing/billing-and-usage/) for plan-specific limits.

Neon

Neon uses [scale-to-zero](https://neon.tech/docs/introduction/auto-suspend) to suspend idle compute automatically. The default idle threshold is 300 seconds, but connection pools, keepalive queries, or monitoring agents can keep the database active.

Track:

`compute_time_seconds`

`suspended_at`

`last_active_at`

`idle_threshold_seconds`

`storage_gb`

`egress_gb`

If a Neon database is not suspending, you are burning compute hours while nothing is using it. That is an expiration failure: the suspend timer never fires.

Build a minimum viable expiration monitor

You do not need a complex billing stack. A scheduled function can fetch usage and state from each provider API, then write rows to Postgres and alert.

type Provider = 'vercel' | 'supabase' | 'netlify' | 'neon';

type UsageSignal = {
  provider: Provider;
  resourceId: string;
  limitType: string;
  observedValue: number;
  limitValue: number;
  expiresAt: Date | null;
};

async function checkExpirations(signals: UsageSignal[]) {
  const now = Date.now();
  const sevenDays = 7 * 24 * 60 * 60 * 1000;

  for (const signal of signals) {
    const usageRatio = signal.observedValue / signal.limitValue;

    if (usageRatio > 0.8) {
      await alert(
        `${signal.provider}:${signal.resourceId} is at ${Math.round(usageRatio * 100)}% of ${signal.limitType}`
      );
    }

    if (signal.expiresAt && signal.expiresAt.getTime() - now < sevenDays) {
      await alert(
        `${signal.provider}:${signal.resourceId} expires in ${daysUntil(signal.expiresAt)} days`
      );
    }
  }
}

Store the raw rows. A time series table makes it easy to answer: 'Which resource grew fastest this month?' That is the question that catches bill shock before it happens.

What causes most serverless bill shock?

**Forgotten cron or webhook** — every invocation counts, and retries multiply.

**Preview deployments** — they receive traffic, consume bandwidth, and stay alive longer than expected.

**Database not scaling to zero** — a connection pooler or health check keeps compute warm.

**Free tier pause/resume cycles** — a paused project resumes with old data and new limits.

**Egress hidden in backups or replication** — storage grows quietly, then billing resets.

How Sapior fits

Sapior schedules provider usage checks, stores the expiration state, and sends alerts before a resource crosses a limit or pauses. It treats serverless resources as lifecycle objects: every edge function, database, and deployment gets an `expires_at` or `reset_at` value.

The result is not just another dashboard. It is an early-warning system for the expiration events that serverless billing turns into invoices.

A cleaner way to think about it

Do not watch spend. Watch expiration.

Spend is a trailing indicator. By the time the number moves, the invocations have happened, the compute hours have burned, and the bandwidth has left your account.

The expiration date, the quota ratio, and the suspend state are leading indicators. They tell you something will go wrong before the invoice arrives.

Track those signals for Vercel, Supabase, Netlify, and Neon, and serverless bill shock becomes an operational problem you can schedule, instead of a financial surprise you cannot undo.

Serverless Bill Shock: Track Edge Function and Database Expirations