<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pavels Gurskis</title>
    <description>The latest articles on DEV Community by Pavels Gurskis (@pavelsg).</description>
    <link>https://dev.to/pavelsg</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3940069%2F46906ae1-618a-4e80-b4f6-05c91f767db4.png</url>
      <title>DEV Community: Pavels Gurskis</title>
      <link>https://dev.to/pavelsg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pavelsg"/>
    <language>en</language>
    <item>
      <title>Normalization Is Where Reliable Inbound Email Starts</title>
      <dc:creator>Pavels Gurskis</dc:creator>
      <pubDate>Sat, 30 May 2026 11:10:29 +0000</pubDate>
      <link>https://dev.to/pavelsg/normalization-is-where-reliable-inbound-email-starts-1boe</link>
      <guid>https://dev.to/pavelsg/normalization-is-where-reliable-inbound-email-starts-1boe</guid>
      <description>&lt;p&gt;Most teams building inbound email systems start with extraction. They focus on parsing headers, pulling out participants, identifying attachments, and mapping everything into a useful event. That makes sense at first, because extraction is where the visible complexity lives. But in practice, reliability usually fails earlier. It fails when two semantically identical emails enter the pipeline and come out as slightly different payloads.&lt;/p&gt;

&lt;p&gt;That is the quiet problem normalization solves. Before matching, routing, deduplication, or analytics can be trusted, the system needs one consistent internal representation of the same message. Without that step, every downstream consumer inherits ambiguity from raw email itself: flexible header syntax, variable address formats, inconsistent timestamp shapes, and unstable ordering in arrays that look harmless until retries or replays start producing different results.&lt;/p&gt;

&lt;p&gt;In this post, I want to make the case that normalization is not cleanup work after parsing. It is the foundation that makes parsing reliable at all. For platform engineers and technical leaders, that means treating canonical field shape, participant ordering, attachment order, and timestamp precision as contract decisions early, not implementation details to patch later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hidden reliability layer starts with canonical field normalization
&lt;/h2&gt;

&lt;p&gt;Teams can spend weeks refining inbound email parsing, extraction, and routing, only to find that the same message arrives in slightly different valid shapes. One payload may include a display name in the sender field, another may split name and address, and another may format dates differently, all while remaining valid email. That is why canonical field normalization acts as a hidden reliability layer before matching, routing, or mapping begins. (&lt;a href="https://datatracker.ietf.org/doc/html/rfc5322" rel="noopener noreferrer"&gt;RFC 5322 - Internet Message Format&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;This problem appears often because internet email was designed to permit flexibility in header fields, address syntax, and date-time representation. RFC 5322 defines a broad message format, so two messages can mean the same thing while looking different at the raw-text level. For platform engineers, the first job is not extraction but deciding what the system will treat as equivalent and storing that equivalence consistently.&lt;/p&gt;

&lt;p&gt;In practice, a stable internal event should be boring: one sender object shape, one timestamp format, one policy for empty fields, and one naming pattern across the schema. That consistency reduces branch logic in downstream services and makes tests, reviews, and incident response easier because engineers are working from a stable contract rather than provider-specific variation.&lt;/p&gt;

&lt;p&gt;Many teams lose reliability by normalizing only after matching starts. When one parser maps raw fields directly, another lowercases on the fly, and a third patches odd cases later, repeatability drops because transformation depends on message path. A dedicated normalization pass near the start keeps the logic visible, testable, and reviewable.&lt;/p&gt;

&lt;p&gt;Define a canonical form before tuning extraction accuracy. Choose the exact internal shape for names, addresses, headers, nulls, booleans, and timestamps, then require every parser, importer, and webhook path to emit that shape first. A quick test is simple: can two semantically identical emails produce the same structured JSON even when their raw text differs? Because valid email syntax is intentionally flexible under RFC 5322, reliable systems answer that question early and in one place.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faviqmpdt9ruas4dh617t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faviqmpdt9ruas4dh617t.png" alt="Canonical field normalization" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Here is where people-array ordering stability quietly protects trust
&lt;/h2&gt;

&lt;p&gt;People-array ordering stability means sender and recipient collections are normalized and emitted in the same predictable order every time the same email is processed. I have seen teams build a clean email JSON schema, pass every parser test they wrote, and still lose trust in production because the people arrays kept moving around. The sender looked the same, the recipients were the same humans, and the message was the same event, yet one run produced a different array order than the next. That sounds small until a downstream service hashes the payload, compares snapshots, or decides whether an inbound email webhook is a duplicate based on structural sameness. (&lt;a href="https://docs.stripe.com/api/idempotent_requests" rel="noopener noreferrer"&gt;Stripe API Reference - Idempotent requests&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Order matters because systems consume structure, not intent. If repeated processing of the same message yields a different practical result, safe retries become harder to reason about. Stripe documents idempotent requests in that spirit: the same request key should return the same result on retry, and parameter mismatches are treated as misuse rather than harmless variation. That is a strong analogy for deterministic payload design in inbound email parsing. Raw email can expose participants through multiple headers and parser outputs, and those sources do not always arrive in a form that is ready for dependable downstream use. A parser may preserve encounter order from the source text, another may rebuild objects from a map, and a third may merge sender and recipient details after enrichment. If you do not choose one stable ordering rule, your structured email JSON output can drift even when the underlying message meaning has not changed.&lt;/p&gt;

&lt;p&gt;Treat people-array ordering as part of the contract, not a formatting detail. Write the ordering rule down, test it with replays, and enforce it before matching, routing, or analytics begin. A simple check is whether the same raw message, when retried, replayed, or re-imported, produces sender and recipient arrays that are byte-for-byte stable after normalization. If not, trust is already leaking out of the system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvjgpba3pbnv7msrfigl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flvjgpba3pbnv7msrfigl.png" alt="People-array ordering stability" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Attachments need deterministic order for replayable output
&lt;/h2&gt;

&lt;p&gt;The same email can contain the same files and bytes yet still produce a different payload on replay if attachment order is left to chance. In an email JSON schema, that small shift can change diffs, break snapshot tests, and blur whether the system saw a new event or simply serialized the old one differently. (&lt;a href="https://datatracker.ietf.org/doc/html/rfc6376" rel="noopener noreferrer"&gt;RFC 6376 - DomainKeys Identified Mail (DKIM) Signatures&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Attachment metadata is often assembled across multiple stages during inbound email parsing, so outputs can vary unless ordering is defined explicitly. One parser may preserve MIME encounter order, another may group inline files separately, and another may rebuild arrays from storage results. If no ordering rule exists, repeated processing can produce structurally different but equally valid outputs. That matters because replay safety, comparisons, and verification all work better with stable representation. DKIM is a useful precedent: it defines canonicalization algorithms to reduce variation in message representation before verification. A practical policy is to choose one stable ordering rule and apply it everywhere, such as original MIME position, then filename, then content type, then byte size.&lt;/p&gt;

&lt;p&gt;Use a simple replay test: run the same raw message through the pipeline multiple times and confirm the attachment array is identical each time. If it is, retries stay calmer, diffs stay meaningful, and production behavior is easier to trust. If it is not, make ordering an explicit contract instead of an accidental side effect.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fra0u4gni3ytrmp69v3td.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fra0u4gni3ytrmp69v3td.png" alt="Attachment ordering determinism" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Time gets simpler when I choose whole-second UTC precision
&lt;/h2&gt;

&lt;p&gt;Time is where a trustworthy pipeline can start to feel haunted. I replay the same message through an email parsing API, and the business meaning stays the same, yet one run says &lt;code&gt;2026-05-30T14:22:31.124Z&lt;/code&gt;, another keeps the original &lt;code&gt;-0700&lt;/code&gt; offset, and a third lands as a local server time string. That kind of drift makes a clean email JSON schema feel less stable than it looks, because engineers stop asking whether the event changed and start asking whether the clock changed shape.&lt;/p&gt;

&lt;p&gt;You might be wondering: why do I care so much if the times all point to the same moment? I care because comparison, replay, and debugging all depend on one shared representation. RFC 5322 allows Internet message date-time fields to carry timezone offsets and flexible representational details, which means valid raw emails can describe time in more than one acceptable way before they ever reach my parser. If I pass that variation downstream, every consumer has to make its own judgment about equivalence.&lt;/p&gt;

&lt;p&gt;That is where whole-second UTC precision earns its place. I choose one universal timezone, one precision, and one serialization rule before matching, routing, deduplication, or analytics begin. Now retries are easier to reason about because the same message produces the same timestamp shape on every pass. Incident review gets faster too. When logs, payloads, and internal events all speak the same time format, I can line up message receipt, parser execution, webhook delivery, and downstream writes without doing mental offset math.&lt;/p&gt;

&lt;p&gt;This also helps with boundary cases that quietly waste engineering time. A provider may preserve sub-second values from one ingestion path and drop them on another. A mailbox import may expose a numeric offset, while an inbound email parsing flow may already emit a UTC string. A replay job may use a different library default than the real-time path. Each of those choices can create structurally different output even when the underlying event is identical. When I normalize to whole-second UTC precision, I remove a class of accidental differences before they leak into tests, hashes, diffs, and audit trails.&lt;/p&gt;

&lt;p&gt;I am careful here for one reason: time fields often become anchors in a deterministic JSON email payload. Teams sort by them, compare by them, and explain production behavior with them. If the timestamp contract is vague, every downstream rule inherits that vagueness.&lt;/p&gt;

&lt;p&gt;My practical rule is simple: parse the raw message time, convert it to UTC, keep whole seconds, and emit one exact format everywhere. Then I test replay, reimport, and retry paths to confirm they produce the same timestamp value for the same message. Once that rule is in place, time becomes boring in the best way. Comparisons get cleaner, debugging gets shorter, and the stable JSON contract email systems need starts to hold under pressure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3qvi4ov3rllf8l18dq8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3qvi4ov3rllf8l18dq8.png" alt="Whole-second UTC precision" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reliable inbound email processing is less about clever parsing than about disciplined sameness. If your system allows equivalent messages to turn into different structured outputs, every retry, replay, diff, and incident review becomes harder than it should be. The damage is subtle at first, then expensive.&lt;/p&gt;

&lt;p&gt;That is why normalization deserves to sit near the start of the pipeline. A defined internal shape for fields, stable ordering for people and attachments, and one exact rule for timestamps give the rest of the stack something dependable to build on. Once those decisions are explicit and enforced everywhere, downstream logic gets simpler, test coverage gets stronger, and trust in the payload starts to compound.&lt;/p&gt;

&lt;p&gt;The practical question is straightforward: when the same message is processed twice, do you get the same JSON back? If the answer is not an easy yes, normalization is where reliability work should begin.&lt;/p&gt;

</description>
      <category>idempotence</category>
      <category>replay</category>
      <category>deduplication</category>
    </item>
    <item>
      <title>Email Monitoring with MailWebhook and Zabbix</title>
      <dc:creator>Pavels Gurskis</dc:creator>
      <pubDate>Tue, 19 May 2026 11:15:20 +0000</pubDate>
      <link>https://dev.to/pavelsg/email-monitoring-with-mailwebhook-and-zabbix-20gc</link>
      <guid>https://dev.to/pavelsg/email-monitoring-with-mailwebhook-and-zabbix-20gc</guid>
      <description>&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;A cron job sends a canary email through an external sender.&lt;/li&gt;
&lt;li&gt;The sender resolves MX and delivers to the mailbox under test.&lt;/li&gt;
&lt;li&gt;MailWebhook watches that mailbox.&lt;/li&gt;
&lt;li&gt;When the canary arrives, MailWebhook calls Zabbix &lt;a href="https://www.zabbix.com/documentation/current/en/manual/api/reference/history/push" rel="noopener noreferrer"&gt;&lt;code&gt;history.push()&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Zabbix alerts if no OK heartbeat arrives within the expected window.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key idea is to alert on missing success, not on every possible delivery failure.&lt;/p&gt;

&lt;p&gt;When the canary arrives, MailWebhook calls Zabbix &lt;a href="https://www.zabbix.com/documentation/current/en/manual/api/reference/history/push" rel="noopener noreferrer"&gt;&lt;code&gt;history.push()&lt;/code&gt;&lt;/a&gt;. Zabbix alerts only if no OK heartbeat arrives within the expected window.&lt;/p&gt;

&lt;p&gt;The key idea is to alert on missing success, not on every possible delivery failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Canary tagging
&lt;/h2&gt;

&lt;p&gt;Do not send a generic test email. Send a tagged canary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Subject: MW-CANARY v=1 env=prod mailbox=mx1-prod sender=postmark id=&amp;lt;uuid&amp;gt; ts=&amp;lt;utc&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each tag has a job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the first version, Zabbix only needs the OK heartbeat. This canary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MW-CANARY v=1 env=prod mailbox=mx1-prod sender=postmark id=7f3a9c ts=2026-05-18T12:00:00Z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;maps to this Zabbix item key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mail.canary.ok[prod,mx1-prod]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Zabbix setup
&lt;/h2&gt;

&lt;p&gt;Create a host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host: mail-deliverability
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create one &lt;a href="https://www.zabbix.com/documentation/current/en/manual/config/items/itemtypes/trapper" rel="noopener noreferrer"&gt;Zabbix trapper item&lt;/a&gt; per mailbox path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Name: Canary OK: prod mx1-prod
Key: mail.canary.ok[prod,mx1-prod]
Type: Zabbix trapper
Type of information: Numeric unsigned
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add a &lt;a href="https://www.zabbix.com/documentation/current/en/manual/config/triggers/expression" rel="noopener noreferrer"&gt;trigger expression&lt;/a&gt; using &lt;code&gt;nodata()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nodata(/mail-deliverability/mail.canary.ok[prod,mx1-prod],10m)=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example tuning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Send interval: 1 minute
Warning: nodata(...,5m)=1
Critical: nodata(...,10m)=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For noisier paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Send interval: 5 minutes
Warning: nodata(...,15m)=1
Critical: nodata(...,30m)=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  MailWebhook endpoint
&lt;/h2&gt;

&lt;p&gt;Create a &lt;a href="https://docs.mailwebhook.com/docs/endpoints/" rel="noopener noreferrer"&gt;MailWebhook endpoint&lt;/a&gt; pointing to the Zabbix JSON-RPC API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://zabbix.example.com/zabbix/api_jsonrpc.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a custom header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Authorization: Bearer &amp;lt;ZABBIX_API_TOKEN&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  MailWebhook route
&lt;/h2&gt;

&lt;p&gt;Use &lt;a href="https://docs.mailwebhook.com/docs/routes/rules/" rel="noopener noreferrer"&gt;MailWebhook route rules&lt;/a&gt; to match only canary emails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"to_emails"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"deliverability-check@example.com"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"from_domains"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"your-canary-sender.example"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject_regex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"^.*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;bMW-CANARY&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b.*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;benv=[A-Za-z0-9_-]+&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b.*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;bmailbox=[A-Za-z0-9_-]+&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b.*$"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  MailWebhook custom JSON payload
&lt;/h2&gt;

&lt;p&gt;Use &lt;a href="https://docs.mailwebhook.com/docs/routes/pipeline/custom_json/" rel="noopener noreferrer"&gt;&lt;code&gt;map.custom_json&lt;/code&gt;&lt;/a&gt; to emit the Zabbix &lt;code&gt;history.push()&lt;/code&gt; request. The expressions below use the MailWebhook &lt;a href="https://docs.mailwebhook.com/docs/routes/pipeline/custom_json/dsl/" rel="noopener noreferrer"&gt;JsonLogic-style DSL&lt;/a&gt; for &lt;code&gt;regex.replace&lt;/code&gt; and &lt;code&gt;cat&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pipeline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"steps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"map.custom_json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"vars"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"expr"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"regex.replace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"var"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"message.subject"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^.*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;benv=([A-Za-z0-9_-]+)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b.*$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"with"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;1"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mailbox_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"expr"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"regex.replace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"var"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"message.subject"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"pattern"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^.*&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;bmailbox=([A-Za-z0-9_-]+)&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;b.*$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"with"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;1"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zabbix_key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"expr"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"cat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="s2"&gt;"mail.canary.ok["&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"var"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vars.env"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="s2"&gt;","&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"var"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vars.mailbox_id"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="s2"&gt;"]"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"history.push"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mail-deliverability"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"var"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vars.zabbix_key"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the example subject above, MailWebhook sends this to Zabbix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jsonrpc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"history.push"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"params"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mail-deliverability"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mail.canary.ok[prod,mx1-prod]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Canary sender
&lt;/h2&gt;

&lt;p&gt;Here is a minimal cron sender:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"prod"&lt;/span&gt;
&lt;span class="nv"&gt;MAILBOX_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mx1-prod"&lt;/span&gt;
&lt;span class="nv"&gt;SENDER_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"postmark"&lt;/span&gt;
&lt;span class="nv"&gt;TO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"deliverability-check@example.com"&lt;/span&gt;
&lt;span class="nv"&gt;FROM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"canary@your-canary-sender.example"&lt;/span&gt;

&lt;span class="nv"&gt;CANARY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;uuidgen&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;TS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; +%Y-%m-%dT%H:%M:%SZ&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;SUBJECT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"MW-CANARY v=1 env=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ENV&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; mailbox=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MAILBOX_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; sender=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SENDER_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; id=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CANARY_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; ts=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt; RANDOM &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'deliverability canary\n'&lt;/span&gt; | mail &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SUBJECT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FROM&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TO&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cron:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* * * * * /opt/mail-canary/send-canary.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The jitter prevents all checks from landing at the exact same second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Operational notes
&lt;/h2&gt;

&lt;p&gt;Simple start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;One mailbox path -&amp;gt; one Zabbix trapper item -&amp;gt; one nodata trigger
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add dimensions only when needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mail.canary.ok[prod,mx1-prod]
mail.canary.ok[prod,mx1-prod,postmark]
mail.canary.ok[prod,mx1-prod,ses]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multi-sender checks are useful when you need to distinguish mailbox-path failures from sender-path failures.&lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;history.push()&lt;/code&gt; to Zabbix.&lt;/p&gt;

</description>
      <category>zabbix</category>
      <category>smtp</category>
      <category>monitoring</category>
    </item>
  </channel>
</rss>
