Guide

Send Incident Emails from the CLI

Send severity-gated incident email from a shell script with nylas email send. Run it as a lightweight notifier alongside PagerDuty, with no SMTP host, no Postfix daemon, and no mail server to maintain.

Written by Pouya Sanooei Software Engineer

Reviewed by Qasim Muhammad

VerifiedCLI 3.1.17 · Gmail, Outlook · last tested June 9, 2026

Why send an incident email notification from the CLI?

An incident email notification is a severity-gated message that a monitoring script sends when a service crosses an alert threshold. The Nylas CLI sends it with one command: store an API key as an environment variable, call nylas email send, and the message goes out over OAuth2 with no SMTP host. Setup takes under 60 seconds.

PagerDuty handles escalation, on-call rotation, and acknowledgement. Email is the durable, searchable record of what happened. The two jobs are different, and the 2017 Google SRE practice of separating “page a human” from “file a record” still holds. Use the CLI for the record half so a stakeholder who is not on the rotation — a support lead, an account manager, a compliance auditor — gets the same context the responder saw, without a PagerDuty seat.

The tool keeps the script small. There is no Postfix daemon on the host, no TLS certificate to renew, and no provider SDK in your alert code. One binary reads the API key and sends.

How do I gate incident email on severity?

Gate on severity by comparing the incoming level against a threshold before you call the send command. PagerDuty defines four severity values — critical, error, warning, and info — in its Events API v2. A case statement that returns early for the bottom two levels stops roughly 60% of alert volume from ever reaching an inbox.

The script below reads a severity argument and exits silently for warning and info. Only critical and error proceed to the send step. This is the single most effective noise filter you can add, because alert fatigue causes responders to mute or filter channels, and a muted channel is worse than no channel.

#!/usr/bin/env bash
# notify.sh SEVERITY "SERVICE" "SUMMARY"
set -euo pipefail

SEVERITY="$1"
SERVICE="$2"
SUMMARY="$3"

case "$SEVERITY" in
  critical|error) ;;            # send
  *) echo "skipping email for $SEVERITY"; exit 0 ;;
esac

nylas email send "$NYLAS_GRANT_ID" \
  --to "$ALERT_EMAIL_TO" \
  --subject "[$SEVERITY] $SERVICE incident" \
  --body "Severity: $SEVERITY
Service: $SERVICE
Summary: $SUMMARY" \
  --yes \
  --json

The grant ID is the first positional argument to nylas email send; the API key comes from NYLAS_API_KEY in the environment. Pass the severity as $1 from whatever triggers the script so the same wrapper serves a cron health check, a webhook handler, or a PagerDuty custom action.

What should an incident email body include?

An incident email body should answer four questions in the first three lines: what broke, how bad it is, which service, and where to look. RFC 5424, the syslog standard, encodes severity as a number from 0 (emergency) to 7 (debug); mirror that idea by putting the severity word first so a recipient scanning on a phone sees urgency before detail. Keep the body under 15 lines.

Send HTML when you want a clickable runbook link and a colored severity banner. The --body flag accepts HTML or plain text, so an anchor tag in an HTML body becomes a real link. The example below builds the body in a heredoc, which keeps the script readable and lets you interpolate the dashboard URL and incident ID without escaping quotes.

BODY=$(cat <<HTML
<h2 style="color:#c00">[$SEVERITY] $SERVICE</h2>
<p><strong>Summary:</strong> $SUMMARY</p>
<ul>
  <li>Dashboard: <a href="$DASHBOARD_URL">$DASHBOARD_URL</a></li>
  <li>Incident ID: $INCIDENT_ID</li>
  <li>Owner: platform-oncall@example.com</li>
</ul>
HTML
)

nylas email send "$NYLAS_GRANT_ID" \
  --to "$ALERT_EMAIL_TO" \
  --subject "[$SEVERITY] $SERVICE incident" \
  --body "$BODY" \
  --yes \
  --json

Do not paste full logs into the body. A two-line error excerpt plus a dashboard link routes faster than 200 lines of stack trace. Logs belong in your observability tool, where search and retention already exist.

Why tag and track incident sends?

Tag incident sends so you can prove an alert was delivered and correlate it back to the source incident. The nylas email send command accepts --metadata key=value pairs, up to 50 per message, with five indexed keys (key1 through key5) that support filtering in later API queries. Stamp the PagerDuty incident ID and severity onto every message.

Add --track-opens when you need to know a human actually saw a critical page, not just that the message left the server. The flag records an open event, which matters during a postmortem when the question is whether the alert reached anyone before the 30-minute escalation window elapsed. The send below stamps two indexed keys and turns on open tracking.

nylas email send "$NYLAS_GRANT_ID" \
  --to "$ALERT_EMAIL_TO" \
  --subject "[$SEVERITY] $SERVICE incident" \
  --body "$BODY" \
  --metadata key1="$INCIDENT_ID" \
  --metadata key2="$SEVERITY" \
  --track-opens \
  --track-label "incident" \
  --yes \
  --json

Capture the JSON output and write the returned message ID to your incident timeline. That gives support and engineering a way to confirm the notification was sent without searching inboxes by hand.

How does this run alongside PagerDuty?

Run the CLI script as a second, parallel sink — never as a replacement for the pager. PagerDuty triggers the on-call phone and tracks acknowledgement; the script files the email record at the same instant. Wire it into a PagerDuty webhook extension or a custom event action so both fire from one trigger. PagerDuty's Events API v2 expects a JSON payload with a severity field, which maps directly to the $SEVERITY argument in the wrapper.

Keep the email recipient list distinct from the on-call rotation. The rotation gets paged; the email list is for people who need the context but not the 3 a.m. phone call. A typical split sends to a shared incidents@ inbox plus one or two team leads. Review that list monthly — in practice an incident list grows to 12 recipients within a quarter, and an alert nobody can act on is noise.

# PagerDuty custom action calls this with its payload fields:
./notify.sh "$PD_SEVERITY" "$PD_SERVICE_NAME" "$PD_INCIDENT_TITLE"

# Cron health check reuses the same wrapper:
if ! curl -fsS https://api.example.com/health >/dev/null; then
  ./notify.sh critical "api" "health check failed"
fi

Because the wrapper takes plain arguments, the same script serves PagerDuty, a cron probe, and a deploy hook. One notifier, three triggers, no duplicated send logic.

Next steps