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.
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.
curlandjqinstalled.
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>&1Three details that bite people:
- Use absolute paths. Cron's PATH is minimal. Spell out
/usr/bin/curlif 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 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