Spend Guards
What this is
Spend Guards watch the same-day spend of each ad account you switch them on for. They do two separate things:
- Velocity alert. If an account spends a set share of its daily budget inside roughly one hour, Launchpad posts to Slack. It only alerts; it never changes anything. The "daily budget" is yesterday's actual spend for that account, unless you type a manual baseline.
- Hard cap. Each account can have a daily spend cap in dollars. When today's spend reaches the cap, Launchpad pauses the active campaigns in scope and posts to Slack. The cap is independent of the daily budget.
Spend Guards never turn campaigns back on by themselves. Someone has to review the account, turn campaigns on in the ad platform, and re-arm the guard.
Detection lags the ad platform by about 30 to 45 minutes, because platforms report spend late and Launchpad checks every 15 minutes.
Who can open it
The Spend Guards entry in the Admin sidebar section appears only for people who hold the manage_spend_guards permission. No role has it by default except Super Admin; an administrator can grant it to one person or to a custom role. Only a Super Admin can grant or remove it, because it pauses live ad campaigns. Access is re-checked against the database on every request, so a new grant works without signing in again and a revoked one stops working immediately.
Common questions
What are the three modes?
- Alert only — velocity alerts only. The hard cap is never evaluated.
- Dry run — when the cap is reached, Slack says which campaigns would be paused. Nothing is paused. A hard cap is required.
- Live — when the cap is reached, the active campaigns in scope are paused and the result (which campaigns, and any that failed) is recorded and posted. A hard cap is required, and turning Live on asks you to type the account name to confirm.
A guard that is switched off does nothing.
Which campaigns get paused?
The guard's campaign scope decides:
- Only ours (name prefix) — only campaigns whose name starts with the agency prefix. The default is
do-on Google andDO-orDO -on Meta (Instagram accounts follow Meta). You can replace the defaults with your own exact-case prefixes for an account with a different naming convention. - All — every active campaign in the account.
Widening the scope or changing prefixes asks for confirmation, because it changes what a live trip can pause.
What does "No baseline" mean?
The velocity alert needs yesterday's spend (or a manual baseline). If yesterday's spend is unavailable or zero and no override is set, the velocity rule is skipped for that account rather than guessing. The hard cap still works.
How often will it alert?
At most one velocity alert per account every two hours. A hard-cap trip happens at most once per account per day for each of dry-run and live.
The guard says Tripped. What now?
It stays tripped until someone presses Re-arm on the account. Re-arming does not turn campaigns on; do that in Meta or Google first if you want them running.
Can it actually pause on my platform?
The account drawer has a write access check that reports Ready, Unknown, or Missing for pausing campaigns on that account. Run it before you switch an account to Live.
Where do problems show up?
If a Slack post cannot be delivered, or a check keeps failing, the problem appears for Super Admins on the Issues page and in the sidebar bell, and clears itself once the next attempt works.
How to use it
- Open Spend Guards in the Admin section. The cards at the top show guarded accounts, accounts tripped now, alerts in the last 24 hours, and the time of the last check.
- On the Guards tab, filter by client, platform, mode, or state, and sort by columns such as hard cap, last hour, or percentage of cap. Open an account to set its mode, alert percentage (1 to 100, default 20), hard cap, campaign scope, optional daily-budget baseline, and Slack channel.
- Start with Dry run and watch the Slack posts for a few days before switching to Live.
- The Activity tab lists every alert, trip, pause, re-arm, and settings change, filterable by account and kind.
- The Daily summary tab sets up a once-a-day Slack post: on or off, channel, send time (15-minute steps) and time zone, the message templates (with a preview that renders like Slack), and which accounts are listed. The guards run whether or not an account is listed in the summary. A Super Admin can also post a one-time preview of every alert type to check the Slack wiring.
- A daily spend cap can also be set from the Ad accounts tab of a client (the account's spend cap row).
All amounts are dollars (for example 15000 means $15,000.00).
For developers — routes, guards, and internals
- Route:
/admin/spend-guards(tabsguards,activity,summary, selectable with?tab=). - Guard: the segment layout calls
hasSpendGuardAccess(live database re-check of themanage_spend_guardspermission, then redirects to/admin/clients). API routes under/api/admin/spend-guards/*userequireSpendGuardAccess. Escalation ceiling: onlysuper_adminmay assign a custom role containingmanage_spend_guardsor add or remove that override (src/app/api/admin/users/[id]/route.ts). - Scheduling: Inngest
spend-guard-tick(cron4,19,34,49 * * * *) fans outspend-guard-check-account(concurrency 5 overall, 1 per ad account).spend-guard-daily-summaryruns on9,24,39,54 * * * *and sends when the configured local time is reached. - Engine:
src/lib/spend-guards/engine.ts. Last-hour spend is computed from stored snapshots (2 hour look-back); velocity cooldown is 2 hours; trips are claimed once per account per day per kind (cap_trip/cap_trip_dry) before any side effect, and the pause and Slack steps are fail-closed. - Money: all values are decimal dollars. Google
cost_microsare converted once at the fetch edge inplatforms.ts. - Scope rule:
src/lib/spend-guards/scope.tsis the single definition of the agency prefix rule, and the reporting "agency-managed campaigns" filter reuses it, so reporting and pausing cannot disagree. - Failures: Slack delivery failures and failed checks become Issues (
spend_guard_slack,spend_guard_check). - Design record: Spend Guards spec.