How to Escalate Emailed Critical Alerts to Slack and On-Call with a Mailhook
Catch critical monitoring alert emails at a Spojit Mailhook, let an Agent-mode Connector node triage severity, log every alert to MongoDB, and page on-call in Slack behind a human approval gate so only real incidents wake someone up.
What This Integration Does
Monitoring tools, uptime checkers, and infrastructure vendors love to email alerts. The problem is volume: dozens of low-severity notices bury the one message that means production is on fire. This workflow puts a Spojit Mailhook in front of that inbox so every alert email starts an automated run within seconds. An Agent-mode Connector node reads each alert and decides how serious it is. High-severity incidents are escalated to a Slack channel and an on-call engineer is paged, while everything is written to a MongoDB collection so you keep a full, queryable history of what came in and what you did about it.
The run is fully push-driven: mail arriving at the Mailhook address fires the workflow with no mailbox polling and no OAuth. Each message is processed once (Spojit deduplicates per message), the alert text flows through triage into MongoDB, and high-severity alerts pause at a Human node until an on-call lead approves the page. Approved incidents post to Slack and, optionally, send a follow-up email; rejected or timed-out escalations halt without paging anyone. Because the Mailhook is always asynchronous, there is no reply to the sender: any human-facing notice goes out through Slack or a Send Email node.
Prerequisites
- A Slack connection added under Connections, with permission to post to your incident channel and to look users up by email. See the Slack connector article for setup.
- A MongoDB connection pointing at the database where you want to store the alert log (for example a
monitoringdatabase with analertscollection). - The Slack channel ID of your incident channel (for example
C0123ABCDEF), copied from Slack's channel details. - At least one teammate, role, or team in your workspace to act as an approver for the on-call paging gate.
- The destination email address your monitoring tool currently sends alerts to, so you can repoint or forward it to the Mailhook.
Step 1: Add a Mailhook trigger and generate the address
Create a new workflow and open the Trigger node. Set Trigger Type to Mailhook. Enter an Address prefix such as alerts (1 to 24 characters; defaults to mh), then click Generate email address. Spojit creates a unique address like alerts-9f2c1d77ab3e4d05@mailhook.spojit.com. Copy it and point your monitoring tool's notification email (or a mailbox forwarding rule) at it. To cut noise, set a Subject regex like (?i)alert|incident|down|critical and, if your alerts always come from one sender, a From allowlist. The trigger fires whether the address is in To, Cc, or Bcc, and exposes the parsed email as {{ input }} with fields such as {{ input.subject }}, {{ input.text }}, {{ input.from }}, and {{ input.replyTo }}.
Step 2: Triage severity with a Connector node in Agent mode
Add a Connector node in Agent mode (or use a Knowledge query if you keep a runbook; here we use Agent mode for judgment). Give it a prompt that asks the agent to classify the alert and pull out the key facts, and attach a Response Schema so the output is reliable JSON you can branch on. For example, prompt with:
You are triaging an infrastructure alert email.
Subject: {{ input.subject }}
Body: {{ input.text }}
Classify severity as one of: critical, high, medium, low.
Identify the affected service and a one-line summary.
Set the Response Schema to force structured fields:
{
"type": "object",
"properties": {
"severity": { "type": "string", "enum": ["critical", "high", "medium", "low"] },
"service": { "type": "string" },
"summary": { "type": "string" }
},
"required": ["severity", "service", "summary"]
}
Store the result in an output variable such as triage so later steps can read {{ triage.severity }}, {{ triage.service }}, and {{ triage.summary }}. If you are unsure how to phrase the prompt, ask Miraxa in the designer: "Add a Connector node in Agent mode that classifies the alert in {{ input.text }} by severity and returns JSON."
Step 3: Log every alert to MongoDB
Add a Connector node in Direct mode for the MongoDB connector and pick the insert-documents tool. Point it at your monitoring database and alerts collection, and map a single document built from the trigger and triage output:
{
"subject": "{{ input.subject }}",
"from": "{{ input.from }}",
"severity": "{{ triage.severity }}",
"service": "{{ triage.service }}",
"summary": "{{ triage.summary }}",
"messageId": "{{ input.messageId }}",
"receivedAt": "{{ input.receivedAt }}",
"paged": false
}
This gives you a permanent, queryable record of every alert, including the ones that did not warrant a page. Later you can run find-documents or count-documents against the same collection to report on alert volume by service or severity. Save the result to a variable like logResult.
Step 4: Branch on severity with a Condition node
Add a Condition node that checks whether this alert deserves a page. Set the condition to test {{ triage.severity }} against the value critical (you can broaden it to also match high if your on-call policy requires it). Connect the false branch to the end of the workflow so low and medium alerts stop after being logged in Step 3. Connect the true branch into the approval gate in the next step. This keeps the noisy majority of alerts out of Slack and out of anyone's phone.
Step 5: Gate the page behind a Human approval node
On the true branch, add a Human node so a person confirms before on-call is paged. Set a clear Label like Page on-call and a templated Message such as {{ triage.service }} is {{ triage.severity }}: {{ triage.summary }}. Set Urgency to High, add a Timeout (minutes) (for example 10) so a stalled approval does not hang forever, and turn on Email approvers if your leads are not always in the app. Add one Approval slot and place your on-call lead User, an on-call Role, or an on-call Team as an atom in it; any atom in the slot satisfies it. Approvers respond in the Approvals inbox at /approvals. On approval the node outputs { approved: true, approvalId, outcome: "APPROVED" } and the run continues. A reject or a timeout halts the workflow, so nobody is paged on a false alarm.
Step 6: Page on-call in Slack
After the approval, add a Connector node in Direct mode for the Slack connector. First call lookup-user-by-email with the on-call engineer's address in the email field to resolve their Slack user ID, and save it to a variable like oncall. Then add a second Slack Connector node using send-message with channel set to your incident channel ID (for example C0123ABCDEF) and a text value that mentions the on-call user and summarizes the incident:
:rotating_light: *{{ triage.severity }}* on {{ triage.service }}
{{ triage.summary }}
Paged: <@{{ oncall.user.id }}>
From alert: {{ input.subject }}
Capture the returned message timestamp (the ts field) in a variable so any follow-up replies can be threaded with thread_ts. To roll up status updates under the original alert, post follow-ups with send-message and set thread_ts to that saved timestamp.
Step 7: Confirm the page and mark the alert as handled
Optionally add a Send Email node to confirm the escalation to the original sender or an incident distribution list, setting Recipients to {{ input.replyTo }} and a templated Subject and Body that reference {{ triage.service }}. Remember that external recipients must be on your org allowlist under Settings > General > Email recipients. Finally, add a MongoDB Connector node in Direct mode using update-documents to flip the logged record, filtering on {{ input.messageId }} and setting paged to true along with the approvalId from the Human node. This closes the loop so your alert log distinguishes paged incidents from logged-only ones.
Tips
- Keep the triage Response Schema tight (a fixed
enumfor severity) so the Condition node always has a clean value to test. Free-text severity is hard to branch on reliably. - Use a persistent MongoDB index on
messageIdso the Step 7update-documentscall matches exactly one record and yourfind-documentsreports stay fast. - Set the Human node Timeout (minutes) to match your on-call SLA. A timeout is treated as a reject and halts the run, so pick a value you are comfortable failing closed on.
- Resolve Slack users with
lookup-user-by-emailrather than hardcoding IDs; rotating on-call staff then only changes the email you pass in.
Common Pitfalls
- Mailhooks are always asynchronous, so there is no reply to the sender. If you need to acknowledge an alert, send it through Slack or a Send Email node to
{{ input.replyTo }}, not by returning a value. - The Human node does not support "on reject do X" branching: a reject or timeout halts the workflow. Put the MongoDB log (Step 3) before the approval gate so every alert is recorded even when the page is declined.
- If alerts stop arriving, check your Mailhook From allowlist and Subject regex. An over-strict regex silently drops messages that do not match.
- The Slack
send-messagechannelfield expects a channel ID (likeC0123ABCDEF), not the#channel-namedisplay name. Copy the ID from Slack's channel details. - Received emails are retained for 30 days. If you regenerate the Mailhook address, the old address dies instantly, so update your monitoring tool before rotating.
Testing
Before pointing live monitoring at the Mailhook, send a test email to the generated address with a subject and body that look like a critical alert, and watch the run appear in execution history within a few seconds. Confirm the triage variable resolves to critical, that a document lands in your MongoDB alerts collection, and that the run pauses at the Human node. Approve it from the Approvals inbox and verify the Slack message posts to your incident channel with the on-call mention. Then send a low-severity test email and confirm the Condition false branch logs it without paging anyone. Once both paths behave, enable the workflow and repoint your monitoring tool.