Monitoring8 min read

Email health monitoring in 10 minutes

Your website has Pingdom, StatusCake, or UptimeRobot. Your email has — nothing. Here is a 10-minute setup that closes that gap with a free API and a cron job.

You already monitor your website. A ping every minute, a 200 OK on the health endpoint, a Slack alert when latency spikes. Email is a blind spot. The SMTP server accepts the message, the ESP dashboard says "delivered," and nobody notices that half the mail is landing in Gmail's spam folder. Until a customer calls support.

This guide walks through a minimum viable email health monitor — one cron entry, one shell script, one webhook — that you can have running in ten minutes. No SaaS subscription, no agents, no dashboards to babysit.

What we are building

A scheduled job that sends a test message to a panel of seed mailboxes, checks whether it landed in the inbox or in spam, and posts to Slack if placement drops below a threshold. Everything runs on any Linux box with cron.

Why website monitoring is not enough

Website monitoring asks one question: is the server responding? Email monitoring has to answer three:

  • Was the mail accepted by the upstream SMTP? (Your ESP already tells you this.)
  • Did it reach the destination MTA at Gmail, Outlook, Yahoo? (Your ESP mostly tells you this.)
  • Did it land in the inbox or in spam? (Your ESP cannot tell you this. Nobody can, except seed mailboxes.)

Only the third question matters for revenue. The first two are hygiene. A seed-based monitor is the only way to answer the one that counts.

Prerequisites

  • A Linux box with cron (VPS, home server, Raspberry Pi).
  • An Inbox Check API key — grab one from the docs. The free tier covers daily checks for one or two domains.
  • A Slack (or Discord, or Teams) incoming webhook URL.
  • curl and jq installed.

The script

Save this as /opt/email-monitor/check.sh and chmod +x it. It creates a test, polls for the result, computes the inbox rate, and posts to Slack if the rate is below 80%.

#!/usr/bin/env bash
set -euo pipefail

API_KEY="${INBOX_CHECK_API_KEY:?missing API key}"
SLACK_URL="${SLACK_WEBHOOK:?missing Slack webhook}"
SENDER_DOMAIN="mail.example.com"
THRESHOLD=80

# 1. Kick off a new test
TEST_ID=$(curl -s -X POST https://check.live-direct-marketing.online/api/check \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"senderDomain\":\"$SENDER_DOMAIN\",\"subject\":\"Monitor ping\",\"html\":\"<p>ok</p>\"}" \
  | jq -r '.id')

# 2. Poll until complete (max 10 min)
for i in {1..60}; do
  STATUS=$(curl -s "https://check.live-direct-marketing.online/api/check/$TEST_ID" \
    -H "Authorization: Bearer $API_KEY" | jq -r '.status')
  [[ "$STATUS" == "complete" ]] && break
  sleep 10
done

# 3. Fetch summary + compute inbox rate
SUMMARY=$(curl -s "https://check.live-direct-marketing.online/api/check/$TEST_ID" \
  -H "Authorization: Bearer $API_KEY")
INBOX=$(echo "$SUMMARY" | jq -r '.summary.inboxCount')
TOTAL=$(echo "$SUMMARY" | jq -r '.summary.total')
RATE=$(( 100 * INBOX / TOTAL ))

# 4. Alert if below threshold
if (( RATE < THRESHOLD )); then
  curl -s -X POST "$SLACK_URL" \
    -H "Content-Type: application/json" \
    -d "{\"text\":\":warning: $SENDER_DOMAIN inbox rate: $RATE% ($INBOX/$TOTAL)\"}"
fi

echo "[$(date -Iseconds)] $SENDER_DOMAIN rate=$RATE% inbox=$INBOX total=$TOTAL"

Wiring it to cron

Open crontab -e and add one line. Run once a day is plenty for baseline monitoring — placement does not swing by the hour.

# Email health check at 06:15 every day
15 6 * * * INBOX_CHECK_API_KEY=ic_live_xxx SLACK_WEBHOOK=https://hooks.slack.com/... /opt/email-monitor/check.sh >> /var/log/email-monitor.log 2>&1

Three details that bite people:

  • Use absolute paths. Cron's PATH is minimal. Spell out /usr/bin/curl if you are paranoid.
  • Redirect stdout and stderr to a log file. A silent failing cron is the worst failure mode.
  • Do not run on the hour. 00:00, 01:00 and 06:00 are saturated with ESP cron jobs; the providers process mail in bursts and your placement will look noisier than it really is.

Picking a threshold

80% is a reasonable starting point for most B2B senders. For cold outreach, 65% is more realistic. For transactional mail where anything below 95% indicates a real problem, crank it up.

Better yet, replace the static threshold with a rolling one. Compare today's rate against the 7-day average and alert on a drop of more than 10 percentage points. That catches real regressions regardless of your baseline.

Static vs rolling thresholds

Static thresholds are easy but wrong for long-tail senders whose baseline sits at 70%. Rolling thresholds are harder to tune but robust. Start static, move to rolling after a week of data.

Extending the monitor

Multiple domains

Loop over a SENDERS list instead of a single SENDER_DOMAIN. One Slack post per domain if the rate is below threshold; one consolidated log line.

Provider breakdown

The API response includes per-provider placement. Gmail may be fine while Outlook is tanking. Add a Gmail-only threshold and an Outlook-only threshold so you can tell them apart.

Persist history

Append results to a CSV or a local sqlite file. A week of history lets you compute rolling averages; a month of history lets you spot slow degradation trends.

# Append to CSV
echo "$(date -Iseconds),$SENDER_DOMAIN,$INBOX,$TOTAL,$RATE" >> /var/log/email-monitor.csv

FAQ

How often should I run this?

Daily is the right default. Hourly gives you noisier data without actionable information — placement does not change on sub-day timescales unless something is actively on fire, at which point you are already investigating.

Does this cost anything?

The Inbox Check free tier covers daily checks for one or two domains. Beyond that, the Developer plan at a fixed monthly price covers up to 500 checks per month — plenty for 15+ sender domains daily.

Can I run this on Vercel Cron or Cloudflare Workers instead of Linux cron?

Yes. The script translates one-to-one to a scheduled worker. Use Vercel Cron, Cloudflare Workers cron triggers, or AWS EventBridge. Persist to Turso, D1, or Upstash Redis instead of a local file.

What if the monitor itself fails silently?

Wrap the script in a dead-man's-switch. Healthchecks.io and Cronitor both offer free plans: your script pings their URL on successful completion, and if the ping stops arriving, they alert you. Two-layer monitoring for one monitor.
Related reading

Check your deliverability across 20+ providers

Gmail, Outlook, Yahoo, Mail.ru, Yandex, GMX, ProtonMail and more. Real inbox screenshots, SPF/DKIM/DMARC, spam engine verdicts. Free, no signup.

Run Free Test →

Unlimited tests · 20+ seed mailboxes · Live results · No account required