惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Docker
人人都是产品经理
人人都是产品经理
小众软件
小众软件
博客园 - Franky
WordPress大学
WordPress大学
Jina AI
Jina AI
Google DeepMind News
Google DeepMind News
I
InfoQ
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
F
Fortinet All Blogs
博客园 - 【当耐特】
IT之家
IT之家
G
Google Developers Blog
J
Java Code Geeks
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
云风的 BLOG
云风的 BLOG
Recent Announcements
Recent Announcements
有赞技术团队
有赞技术团队
V
Visual Studio Blog
U
Unit 42
阮一峰的网络日志
阮一峰的网络日志
月光博客
月光博客
GbyAI
GbyAI
雷峰网
雷峰网

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Email Monitoring with MailWebhook and Zabbix
Pavels Gursk · 2026-05-19 · via DEV Community

SMTP checks can tell you whether a server appears healthy. They do not always tell you whether a real message made it through the path your users depend on.

This recipe uses MailWebhook as the arrival detector and Zabbix as the monitoring system. The goal is simple: send a tagged canary email, report success only when that email arrives, and let Zabbix alert when the success heartbeat goes missing.

Pattern

  1. A cron job sends a canary email through an external sender.
  2. The sender resolves MX and delivers to the mailbox under test.
  3. MailWebhook watches that mailbox.
  4. When the canary arrives, MailWebhook calls Zabbix history.push().
  5. Zabbix alerts if no OK heartbeat arrives within the expected window.

The key idea is to alert on missing success, not on every possible delivery failure.

When the canary arrives, MailWebhook calls Zabbix history.push(). Zabbix alerts only if no OK heartbeat arrives within the expected window.

The key idea is to alert on missing success, not on every possible delivery failure.

Canary tagging

Do not send a generic test email. Send a tagged canary:

Subject: MW-CANARY v=1 env=prod mailbox=mx1-prod sender=postmark id=<uuid> ts=<utc>

Enter fullscreen mode Exit fullscreen mode

Each tag has a job:

MW-CANARY      route this email as a canary
env            avoid mixing prod/staging
mailbox        map the OK to the right Zabbix item
sender         identify the external sender path
id             correlate sent vs received canary
ts             keep sent time for troubleshooting or future latency checks

Enter fullscreen mode Exit fullscreen mode

For the first version, Zabbix only needs the OK heartbeat. This canary:

MW-CANARY v=1 env=prod mailbox=mx1-prod sender=postmark id=7f3a9c ts=2026-05-18T12:00:00Z

Enter fullscreen mode Exit fullscreen mode

maps to this Zabbix item key:

mail.canary.ok[prod,mx1-prod]

Enter fullscreen mode Exit fullscreen mode

Zabbix setup

Create a host:

Host: mail-deliverability

Enter fullscreen mode Exit fullscreen mode

Create one Zabbix trapper item per mailbox path:

Name: Canary OK: prod mx1-prod
Key: mail.canary.ok[prod,mx1-prod]
Type: Zabbix trapper
Type of information: Numeric unsigned

Enter fullscreen mode Exit fullscreen mode

Then add a trigger expression using nodata():

nodata(/mail-deliverability/mail.canary.ok[prod,mx1-prod],10m)=1

Enter fullscreen mode Exit fullscreen mode

Example tuning:

Send interval: 1 minute
Warning: nodata(...,5m)=1
Critical: nodata(...,10m)=1

Enter fullscreen mode Exit fullscreen mode

For noisier paths:

Send interval: 5 minutes
Warning: nodata(...,15m)=1
Critical: nodata(...,30m)=1

Enter fullscreen mode Exit fullscreen mode

This avoids flapping because one delayed email does not immediately create an incident. Zabbix only alerts when the OK heartbeat has been missing for longer than the threshold.

MailWebhook endpoint

Create a MailWebhook endpoint pointing to the Zabbix JSON-RPC API:

https://zabbix.example.com/zabbix/api_jsonrpc.php

Enter fullscreen mode Exit fullscreen mode

Add a custom header:

Authorization: Bearer <ZABBIX_API_TOKEN>

Enter fullscreen mode Exit fullscreen mode

MailWebhook route

Use MailWebhook route rules to match only canary emails:

{
  "to_emails": ["deliverability-check@example.com"],
  "from_domains": ["your-canary-sender.example"],
  "subject_regex": [
    "^.*\\bMW-CANARY\\b.*\\benv=[A-Za-z0-9_-]+\\b.*\\bmailbox=[A-Za-z0-9_-]+\\b.*$"
  ]
}

Enter fullscreen mode Exit fullscreen mode

MailWebhook custom JSON payload

Use map.custom_json to emit the Zabbix history.push() request. The expressions below use the MailWebhook JsonLogic-style DSL for regex.replace and cat.

{
  "pipeline": {
    "steps": [
      {
        "name": "map.custom_json",
        "args": {
          "version": "v1",
          "vars": [
            {
              "name": "env",
              "expr": {
                "regex.replace": {
                  "value": { "var": "message.subject" },
                  "pattern": "^.*\\benv=([A-Za-z0-9_-]+)\\b.*$",
                  "with": "\\1"
                }
              }
            },
            {
              "name": "mailbox_id",
              "expr": {
                "regex.replace": {
                  "value": { "var": "message.subject" },
                  "pattern": "^.*\\bmailbox=([A-Za-z0-9_-]+)\\b.*$",
                  "with": "\\1"
                }
              }
            },
            {
              "name": "zabbix_key",
              "expr": {
                "cat": [
                  "mail.canary.ok[",
                  { "var": "vars.env" },
                  ",",
                  { "var": "vars.mailbox_id" },
                  "]"
                ]
              }
            }
          ],
          "output": {
            "jsonrpc": "2.0",
            "method": "history.push",
            "params": [
              {
                "host": "mail-deliverability",
                "key": { "var": "vars.zabbix_key" },
                "value": 1
              }
            ],
            "id": 1
          }
        }
      }
    ]
  }
}

Enter fullscreen mode Exit fullscreen mode

For the example subject above, MailWebhook sends this to Zabbix:

{
  "jsonrpc": "2.0",
  "method": "history.push",
  "params": [
    {
      "host": "mail-deliverability",
      "key": "mail.canary.ok[prod,mx1-prod]",
      "value": 1
    }
  ],
  "id": 1
}

Enter fullscreen mode Exit fullscreen mode

Canary sender

Here is a minimal cron sender:

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

ENV="prod"
MAILBOX_ID="mx1-prod"
SENDER_ID="postmark"
TO="deliverability-check@example.com"
FROM="canary@your-canary-sender.example"

CANARY_ID="$(uuidgen)"
TS="$(date -u +%Y-%m-%dT%H:%M:%SZ)"

SUBJECT="MW-CANARY v=1 env=${ENV} mailbox=${MAILBOX_ID} sender=${SENDER_ID} id=${CANARY_ID} ts=${TS}"

sleep "$(( RANDOM % 20 ))"

printf 'deliverability canary\n' | mail \
  -s "$SUBJECT" \
  -r "$FROM" \
  "$TO"

Enter fullscreen mode Exit fullscreen mode

Cron:

* * * * * /opt/mail-canary/send-canary.sh

Enter fullscreen mode Exit fullscreen mode

The jitter prevents all checks from landing at the exact same second.

Operational notes

Simple start:

One mailbox path -> one Zabbix trapper item -> one nodata trigger

Enter fullscreen mode Exit fullscreen mode

Add dimensions only when needed:

mail.canary.ok[prod,mx1-prod]
mail.canary.ok[prod,mx1-prod,postmark]
mail.canary.ok[prod,mx1-prod,ses]

Enter fullscreen mode Exit fullscreen mode

Multi-sender checks are useful when you need to distinguish mailbox-path failures from sender-path failures.

One caveat: direct delivery to Zabbix is the simplest implementation, but Zabbix will not validate MailWebhook webhook signatures or a canary HMAC token by itself. For stricter security, put a tiny gateway in front: verify the MailWebhook signature, optionally validate a canary token, then forward history.push() to Zabbix.