Skip to main content
thenping.me is no longer maintained - Crontinel is the active open-source alternative. Read more →
Outcome monitor · Product preview

Completed
is not done.

A background job can exit 0 and still do nothing. Crontinel keeps those as two states. Check in from curl, Node, Python, Sidekiq, or GitHub Actions. The email still goes out when the assistant is off.

curl · Node · Python · Sidekiq · GitHub Actions · Laravel deep package optional · free to start, no card
live.monitor · app.crontinel.com
jobs1,847 latency42ms
exit 0 · process finished 0 records · outcome failed missing count · not healthy
exit 0
can still open
an incident
0
records is a real
result, not a pass
HTTP
one receipt
for any runtime
email
still sends with
the model off

The job exited 0. It processed nothing. The run still looks green.

A URL returning 200 does not tell you the job did the work. Crontinel stores the process status and the business result separately. A missing count is not healthy. A later receipt with a passing result clears the incident.

The gap

The process finished.
The work did not.

Exit 0 stays exit 0. If the rule required records and the receipt says 0, the check fails. The email names that gap. It does not invent a cause.

scheduled.task

Scheduler stops. Site stays up.

A deploy clobbers your cron entry. The app responds 200. No one fires the scheduler. Your nightly emails stop.

GET /health         200 ok
schedule:run        · 17h ago
outcome.failed

Exit 0. Zero records.

reports:generate finished clean. processed_records is 0. The rule wanted at least 1. Crontinel opens the incident anyway.

status              completed · exit 0
processed_records   0 · failed
worker.crash

Worker crashes. Queue is dead.

A worker process died. Your queue manager shows "running" because one worker is still up. Your billing queue is dead. No errors. No alerts. Just silence.

queue.manager       running
worker.emails       · crashed
The receipt

Finished is not
the same as done.

Any runtime posts one JSON receipt. Crontinel compares the count with the rule. The Laravel package attaches that receipt from the scheduler, and can add queue depth and Horizon freshness.

Workers & supervisors

See the worker, not just the URL.

Per-worker status, paused detection, queue depth trend, failed job rate. Monitor every worker type - the moment one blinks, you know.

Missing evidence

A missing count fails.

Zero is a real result. A receipt that omits the count is not treated as healthy.

pulse
60s
Any runtime

One body. No second SDK.

curl, or the same JSON from Node. The ingest key is not the MCP key.

Hosted

The check outlives the job.

An alert on the same machine dies with the host. Crontinel reads the receipt after the process exits.

Alerts

Email, with the model off.

The zero-work alert and the later recovery both send by email. Slack and webhooks are not on the public card yet.

Email
The body

Same JSON from any runtime.

Status stays completed when the process exits 0. The outcome rule is a separate check.

status              completed
exit_code           0
processed_records   0
outcome             failed
Your assistant

The packet cites the zero count.

Cursor, Claude Code, or Codex can read the same incident. The packet says completion is not business success. It does not mark the job healthy.

› did reports:generate succeed?
Process completed, exit 0. Outcome failed: processed_records is 0. Do not mark the job healthy.
How it works

One receipt.
Two states.

01 · POST

Send the HTTP receipt

Any runtime posts the same body. Use the ingest key, not an MCP key. The shell expands $CRONTINEL_INGEST_KEY.

# Node. curl sends this same JSON.
status: "completed"
exit_code: 0
processed_records: 0
02 · CHECK

Exit 0 can still fail

The server keeps the process status. The outcome rule is separate. A minimum of 1 fails a zero count.

run       completed · exit 0
outcome   failed · actual 0
03 · CLEAR

A later count closes it

The email goes out with the model off. A later receipt that passes the rule resolves the incident.

processed_records  1
outcome            passed
The dashboard

The run stays completed.
The outcome can fail.

Run history keeps exit 0. The outcome column is the business result. On Laravel, queue depth and Horizon freshness show up only when that snapshot was reported.

app.crontinel.com/apps/invoice-processor

invoice-processor

production · eu-west-1 1 alert
Setup Monitors History Settings
schedule runs / 24h
1,447
+2.1% vs yesterday
avg run time
842ms
−120ms vs p50
queue depth · total
241
healthy
failed last 1h
12
+12 vs 0
Scheduled tasks14 tasks
TaskLast runDurationStatus
generate:daily-invoices2m ago1.82sok
dunning:send-reminders14m ago940msok
reports:generate17m ago1.2sexit 0 · 0 records
export:accounting-feed29m ago4.1sok
telemetry:roll-up1h 02m ago12.4sslow
cleanup:stale-sessions3h ago220msok
Workers · 3 supervisorsrunning
QueueDepthOldestTrend
default12200ms
invoices1272m 04s
emails6,41312m 04s
analytics88400ms
notifications380ms
Alert

The email still goes out.

Slack and customer webhooks stay off this page until a paid account receives the same failure and recovery.

Checked path

Email

What is checked today

exit 0 stays separate from the outcome
email sends with the model off
HTTP receipt for any runtime
Pricing

Start free.
Paid plans are being prepared.

We are checking paid billing and usage limits before opening paid signups.

Free

For one app.

$0/mo
  • Email when a finished job did no work
  • The same incident for the assistant you already use
Start free

Paid plans

Starter, Pro, and Max are planned.

Their checkout flow and final usage terms are still under review.

See current availability
Receipt

Post this body.

Same JSON from five runtimes. CRONTINEL_INGEST_KEY is the app ingest key. It is not an assistant key.

Node fetch. curl posts this same JSON.
await fetch("https://app.crontinel.com/api/v1/ingest/cron", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.CRONTINEL_INGEST_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    request_key: "run-1",
    command: "reports:generate",
    status: "completed",
    exit_code: 0,
    started_at: "2026-10-06T12:00:00Z",
    finished_at: "2026-10-06T12:00:05Z",
    outcomes: { metrics: { processed_records: 0 } },
  }),
});
Ready?

Stop finding out
from your users.

Free to start, no card. Post one receipt. The email names a finished job that did no work.