Skip to content
TypeUnlocked
StageProduction
DifficultyModerate
JobAutomate task
ForDevOps & infra
ServicesCron
ServicesNotifications
Why HoodyHTTP-native
TypeUnlocked
StageProduction
DifficultyModerate
JobAutomate task
ForDevOps & infra
ServicesCron
ServicesNotifications
Why HoodyHTTP-native
TypeUnlocked
StageProduction
DifficultyModerate
JobAutomate task
ForDevOps & infra
ServicesCron
ServicesNotifications
Why HoodyHTTP-native
TypeUnlocked
StageProduction
DifficultyModerate
JobAutomate task
ForDevOps & infra
ServicesCron
ServicesNotifications
Why HoodyHTTP-native
CRON · EXPIRES_AT · NOTIFICATIONS

An on-call escalation that ages out with your shift

Post a cron entry on Monday with expires_at set to Friday 09:00. The job pages your phone every 30 minutes for four days, then deletes itself the moment the shift ends. The next on-call posts theirs. No PagerDuty schedule, no shared scheduler config, no calendar reminder to disable it.

Read the cron docs

How the shift hands off without a meeting

Three points in the week. The cron entry's existence tracks the shift exactly — no overlap, no gap, no leftover crontab line.

Monday 09:00 → Friday 09:01expires_at carries the shift
MONDAY 09:00

Post the entry

One curl with schedule */30, command pointed at hoody-notifications/push/me, and expires_at = Friday 09:00:00Z. Server returns id e7d3.

MON–FRI

Pings ride your phone

Every 30 minutes for four days the entry runs and pings hoody-notifications. Only your device. The team channel stays quiet.

FRIDAY 09:01

Entry removes itself

At 09:00:00Z the cron service deletes e7d3. The 09:30 tick has nothing to fire. The next on-call has already posted theirs.

Each on-call writes one POST when their shift starts. The expires_at field is the entire handoff protocol — the cron service does the cleanup, on the second.

Two curls — one per shift

The whole rotation protocol is two HTTP calls per week. The on-call posts on Monday, lists on Friday, sees the entry is already gone. There is no shared schedule file to merge into.

monday.sh · m.dossantos
POST · entry
# monday 09:00 — i'm on call until friday 09:00
curl -X POST \
  https://oncall.containers.hoody.com/users/me/entries \
  -H "Content-Type: application/json" \
  -d '["schedule":"*/30 * * * *","command":"curl -fsS hoody-notifications/push/m.dossantos","comment":"on-call wk19","expires_at":"2026-05-08T09:00:00Z"]'

# response
HTTP/1.1 201 Created
{ "id":"e7d3", "expires_at":"2026-05-08T09:00:00Z", "enabled":true }
friday.sh · m.dossantos
GET · audit
# friday 09:01 — the next on-call took over
curl GET https://oncall.containers.hoody.com/users/me/entries

HTTP/1.1 200 OK
[
  // my entry e7d3 is gone — it expired
  // at 09:00 sharp. j.okafor's new
  // entry took over at 09:00:30.
]
# no slack thread, no calendar reminder

No shared scheduler service is involved. The cron entry is owned by the engineer; nothing about the next on-call's setup depends on the previous one cleaning up.

Three things this shape removes from your week

An on-call entry that owns its own lifetime stops three classes of mistakes that PagerDuty configs and calendar reminders cannot.

ROUTING

Pings ride one device — yours

Because the entry's command points at your personal notify endpoint, escalations route to your phone for the duration of your shift, and only that. No accidental team-channel spam at 3am.

OWNERSHIP

No shared config to edit, no PR to merge

There is no escalation_policy.yaml that everyone touches. Each engineer owns their entry. Two on-calls in different time zones can't conflict by editing the same file.

CLEANUP

The handoff is in the data, not the chat

When Friday 09:00 rolls over you don't ask 'wait, am I still getting these?' The entry is already gone. Verifying the handoff is one GET that returns one fewer row.

What the cron service guarantees

Numbers come from the Hoody Cron API. Real limits, not invented ones.

  1. EXPIRY PRECISION1 sec

    Auto-expiration runs against the system clock. A 09:00:00Z expires_at deletes within the same minute the cron tick fires — no 5-minute janitor lag.

  2. FIELDS PER SCHEDULE5

    Standard 5-field cron expressions plus @hourly / @daily / @weekly macros. */30 * * * * is what fires every 30 minutes during your shift.

  3. ISOLATION1 / user

    Each system user has their own crontab. The next on-call's entry lives under their own /users/[name]/entries — never touching yours.

Limits per the Hoody Cron API: managed entries are JSON CRUD with UUIDs and expires_at; raw crontab access available per user; per-user crontab isolation is built in.

When the shift ends, so does the cron entry — automatically.

before · the rotation fileafter · two curls per week
WHAT THE OLD HANDOFF LOOKED LIKEedit oncall.yaml → PR → merge → page someone to disablefour humans · one shared file · still got pinged at 11am friday
WHAT IT LOOKS LIKE NOWPOST /entries [ expires_at: '2026-05-08T09:00:00Z' ]one engineer · one curl · the cron service does the cleanup
Read the cron docs

What this replaces

The standard reach-for-it tools when you want on-call rotations. Each one charges you a service, a config repo, and a handoff ritual. A cron entry with expires_at charges you one POST.

  • PagerDuty escalation policiesPer-seat pricing for what is a curl into your phone
  • Opsgenie schedule rotationsA whole product to figure out who gets pinged this week
  • custom on-call config reposShared YAML that everyone PRs and nobody owns
  • remember to disable cron when handing offA calendar reminder that you set, ignore, and resent on Friday
  • manual escalation cleanupSlack thread at 11am Friday — "hey can someone unsubscribe me"
  • VictorOps rotationsAnother scheduler service for what one expires_at field does

Stop disabling crontab lines on Friday morning. Set expires_at on Monday and forget it.

Read the cron docs

Read the others