← Blog  ·  October 2026

How to Get Email Alerts When a Cron Job Fails (Without Writing a Script)

The data pipeline ran Tuesday. You found out Friday — from a customer asking why their report was blank.

Cron jobs have no built-in failure notification. If a job exits with an error, nothing happens. No email, no Slack message, no red badge in a dashboard. The scheduler just schedules the next run. Your data pipeline breaks quietly, your cleanup job stops cleaning, your backup never backs up — and you find out days later from a user.

This guide covers every realistic option for getting a cron job failure alert email: from the quick one-liner hacks that developers reach for first, to the OS-level tools that actually work, to the monitoring approach that catches failures and missed runs.


Why standard cron email notifications are unreliable

Cron does have a built-in email mechanism. If a job produces output on stdout or stderr, cron will try to mail it to the local system user. In theory, set MAILTO=you@example.com at the top of your crontab and you're done.

In practice, this fails on most modern servers for several reasons:

The real problem Most developers assume "if the job fails, I'll get notified." Most servers don't support that assumption out of the box. The gap between what you expect and what actually happens is where incidents live.

3 ways to add failure alert emails to cron jobs

1. The mail wrapper pattern

The oldest trick: wrap your command in a shell one-liner that sends an email on failure. The pattern uses exit-code checking:

# Send email on failure using mail/sendmail
0 3 * * * /opt/jobs/sync.py || echo "sync.py failed on $(hostname) at $(date)" | mail -s "Cron job failed: sync.py" you@example.com

This works if — and only if — mail is installed and configured to actually deliver messages. On a stock Ubuntu 22 server with mailutils and a configured relay, this is a reasonable 30-second solution. On a container, a Lambda, or a cloud VM without a relay, it delivers nothing.

A slightly more portable version uses curl to hit an email API like SendGrid or Mailgun directly:

0 3 * * * /opt/jobs/sync.py || curl -s -X POST https://api.sendgrid.com/v3/mail/send \
  -H "Authorization: Bearer $SENDGRID_KEY" \
  -H "Content-Type: application/json" \
  -d '{"personalizations":[{"to":[{"email":"you@example.com"}]}],"from":{"email":"alerts@yourapp.com"},"subject":"Cron job failed","content":[{"type":"text/plain","value":"sync.py failed"}]}'

This works anywhere curl is available, but now you're managing an API key in crontab, maintaining an inline JSON payload, and handling the secret securely. It gets messy fast.

2. systemd OnFailure (Linux only)

If your server runs systemd, you can convert your cron job into a systemd timer and use the OnFailure directive to trigger a notification unit when the job fails:

# /etc/systemd/system/sync.service
[Unit]
Description=Nightly sync job
OnFailure=notify-failure@%n.service

[Service]
Type=oneshot
ExecStart=/opt/jobs/sync.py
# /etc/systemd/system/notify-failure@.service
[Unit]
Description=Send failure email for %i

[Service]
Type=oneshot
ExecStart=/usr/bin/bash -c 'echo "Service %i failed" | mail -s "Cron job failed: %i" you@example.com'

This is the most robust OS-level solution for traditional Linux servers. It handles exit codes, crash signals, and timeouts. The downside: it requires root access, doesn't work in containers or serverless environments, and still depends on a working mail stack for the final delivery step.

3. External monitoring service

The most reliable approach, and the only one that works consistently across crontab, Lambda, GitHub Actions, Kubernetes, and Docker, is to use an external monitoring service. Your job pings a URL on success; if the ping doesn't arrive, the service sends you an alert.

This is covered in detail in the next section.


The dead man's switch approach — more reliable than failure detection alone

Alerting on failures is necessary but not sufficient. Consider these scenarios that failure-only alerting misses:

A dead man's switch (also called a heartbeat monitor) flips the detection model: instead of alerting when something goes wrong, you alert when an expected signal stops arriving. Your job sends one HTTP ping at the end of each successful run. If the monitoring service doesn't receive that ping within the expected window, it fires an alert.

# crontab — ping a monitoring service on success
0 3 * * * /opt/jobs/sync.py && curl -sf https://cron.watch/ping/your-job-id

# GitHub Actions — only ping if the job step succeeds
- name: Run sync
  run: python sync.py
- name: Notify CronWatch
  if: success()
  run: curl -sf https://cron.watch/ping/your-job-id

The && operator ensures the ping only fires when the job exits with code 0. A failed job, a killed job, or a job that never runs — all three result in a missing ping, and all three trigger the same alert within minutes of the expected window.

Why this is stronger than email-on-failure A failure-based alert tells you "something went wrong." A dead man's switch tells you "the expected evidence of success never arrived." The second catches a strictly larger set of problems — including the ones that are hardest to detect.

CronWatch: add one curl line, get alerted when the job is late or silent

CronWatch is built around the dead man's switch pattern. You add one line to your cron job — a curl ping to your CronWatch endpoint. CronWatch tracks the expected schedule and sends you an email alert within minutes if a run is late, silent, or failed.

No agent to install. No SMTP server to configure. No wrapper scripts to maintain. Works across every environment that has curl: bare metal crontab, GitHub Actions, Lambda, Kubernetes CronJob, Heroku Scheduler, Cloud Run, Render.

# Before (no monitoring)
0 3 * * * /opt/jobs/nightly-sync.py

# After (CronWatch monitoring — one extra token)
0 3 * * * /opt/jobs/nightly-sync.py && curl -sf https://cron.watch/ping/abc123

You define the schedule and the grace period. If CronWatch doesn't see a ping within that window — whether because the job failed, was killed, or never started — you get an email. You can also configure alerts for when a job starts running but never completes, catching jobs that hang indefinitely.

The result: you stop checking whether your cron jobs are running. You know they're running because CronWatch would have emailed you if they weren't.

📟 Get alerted when your cron jobs fail or go missing

CronWatch monitors your scheduled jobs across every platform — crontab, GitHub Actions, Lambda, Kubernetes, Heroku, Render. One curl line. Email alerts within minutes of a missed or failed run.

Join the CronWatch waitlist → Parse a cron expression free

← All articles  ·  CronWatch  ·  Cron parser tool