Route deployment notices, incident alerts, and release summaries through MultiMail's approval and identity layer — so automated CI events don't flood inboxes unchecked.
GitHub Actions workflows trigger on push, pull request, release, schedule, and dozens of other repository events. When those workflows need to send email — deployment notices, failure alerts, security scan results, release announcements — they typically use raw SMTP or a transactional service with no controls on who can send what to whom.
MultiMail gives GitHub Actions a managed email surface with oversight modes, identity verification, and per-workflow policy enforcement. A workflow step calls the MultiMail REST API directly using a stored secret. The API handles delivery, logging, and — if configured — human approval before any message leaves your domain.
CI pipelines can generate email volume fast. A failed deployment that retries five times produces five alert emails. MultiMail's `gated_send` mode lets you require a human to approve before batches of automated messages go out, while `monitored` mode delivers immediately but logs every send for audit. You pick the tradeoff per workflow, not globally.
Set a workflow's mailbox to `gated_send` or `gated_all` and no message leaves until a human approves it via the MultiMail dashboard or API. Stops alert storms and accidental mass-sends before they reach recipients.
Every email sent through MultiMail is recorded with the sending identity, timestamp, recipient, and approval status. You can correlate email sends back to specific workflow runs using custom headers or subject prefixes.
Each workflow can send from a dedicated mailbox — `[email protected]` or your own domain — so recipients can distinguish deployment notices from incident alerts from release announcements by sender alone.
MultiMail uses Bearer token auth (`MM_API_KEY` stored as a GitHub Actions secret). No SMTP host, port, username, or password to rotate. Tokens can be scoped per mailbox and revoked without touching workflow YAML.
Workflows that loop or retry can easily send duplicate alerts. MultiMail enforces per-mailbox send limits and supports idempotency keys so retried workflow steps don't produce duplicate emails.
MultiMail's oversight, identity, and authorization models are proven correct in Lean 4. When your CI pipeline sends an email, the policy enforcement isn't best-effort — it's mathematically verified.
name: Deploy and notify
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy application
run: ./scripts/deploy.sh
- name: Send deployment notice
if: success()
env:
MM_API_KEY: ${{ secrets.MM_API_KEY }}
run: |
curl -s -X POST https://api.multimail.dev/v1/send_email \
-H "Authorization: Bearer $MM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "[email protected]",
"to": ["[email protected]"],
"subject": "Deployed: ${{ github.repository }} @ ${{ github.sha }}",
"text": "Deployment triggered by ${{ github.actor }} completed successfully.\n\nCommit: ${{ github.sha }}\nBranch: ${{ github.ref_name }}\nWorkflow: ${{ github.run_id }}"
}'A workflow step that calls the MultiMail `send_email` endpoint after a successful deploy. Uses `curl` with a stored API key secret.
name: CI with gated failure alerts
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
id: tests
run: bun test
- name: Queue failure alert (gated)
if: failure()
env:
MM_API_KEY: ${{ secrets.MM_API_KEY }}
run: |
RESPONSE=$(curl -s -X POST https://api.multimail.dev/v1/send_email \
-H "Authorization: Bearer $MM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from": "[email protected]",
"to": ["[email protected]"],
"subject": "[NEEDS APPROVAL] CI failed: ${{ github.repository }}",
"text": "Tests failed on ${{ github.ref_name }}.\nRun: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}\n\nThis message is pending human approval before delivery."
}')
echo "Queued message: $RESPONSE"Send a failure alert only after human approval. The mailbox is configured in `gated_send` mode — the API call queues the message and returns a pending ID. The workflow does not block waiting for approval.
name: Release announcement
on:
workflow_dispatch:
inputs:
version:
description: Release version (e.g. v1.4.0)
required: true
release_notes:
description: One-paragraph release summary
required: true
jobs:
announce:
runs-on: ubuntu-latest
steps:
- name: Send release announcement
env:
MM_API_KEY: ${{ secrets.MM_API_KEY }}
VERSION: ${{ inputs.version }}
NOTES: ${{ inputs.release_notes }}
run: |
curl -s -X POST https://api.multimail.dev/v1/send_email \
-H "Authorization: Bearer $MM_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"from\": \"[email protected]\",
\"to\": [\"[email protected]\"],
\"subject\": \"Released: $VERSION\",
\"text\": \"$NOTES\\n\\nFull changelog: https://github.com/${{ github.repository }}/releases/tag/$VERSION\"
}"
- name: Check pending approvals
env:
MM_API_KEY: ${{ secrets.MM_API_KEY }}
run: |
curl -s https://api.multimail.dev/v1/list_pending \
-H "Authorization: Bearer $MM_API_KEY" | jq '.pending[] | {id, subject, status}'A manually triggered workflow that sends a release announcement to a subscriber list. The `list_pending` endpoint can be polled to confirm approval before the workflow exits.
"cm"># .github/scripts/send_scan_report.py
import os
import json
import sys
from multimail import Multimail
client = Multimail(api_key=os.environ["MM_API_KEY"])
"cm"># Scan results passed as JSON via environment variable
results = json.loads(os.environ.get("SCAN_RESULTS", "{}"))
critical = results.get("critical", [])
high = results.get("high", [])
if not critical and not high:
print("No critical or high findings — skipping alert")
sys.exit(0)
body_lines = ["Security scan completed for " + os.environ["GITHUB_REPOSITORY"]]
body_lines.append("Run: https://github.com/" + os.environ["GITHUB_REPOSITORY"] + "/actions/runs/" + os.environ["GITHUB_RUN_ID"])
body_lines.append("")
if critical:
body_lines.append(f"CRITICAL ({len(critical)}):")
for finding in critical:
body_lines.append(f" - {finding}")
if high:
body_lines.append(f"HIGH ({len(high)}):")
for finding in high:
body_lines.append(f" - {finding}")
response = client.send_email(
from_address="[email protected]",
to=["[email protected]"],
subject=f"[Security] {len(critical)} critical findings in {os.environ[&"cm">#039;GITHUB_REPOSITORY']}",
text="\n".join(body_lines)
)
print(f"Sent: {response.message_id}")A Python script run inside a workflow step that uses the multimail-sdk to send a security scan summary with structured results.
Sign up at multimail.dev and create a dedicated mailbox for CI notifications. Use a subdomain mailbox like `[email protected]` or configure your own domain. Set the oversight mode to `gated_send` for a safe starting point.
"cm"># Via MultiMail API (or use the dashboard)
curl -X POST https://api.multimail.dev/v1/create_mailbox \
-H "Authorization: Bearer $MM_API_KEY" \
-H "Content-Type: application/json" \
-d &"cm">#039;{"address": "[email protected]", "oversight_mode": "gated_send"}'In your GitHub repository, go to Settings → Secrets and variables → Actions → New repository secret. Name it `MM_API_KEY` and paste your `mm_live_...` token. For organizations, store it as an organization secret to share across multiple repositories.
"cm"># Verify the secret is accessible in a test workflow step
- name: Verify MM_API_KEY
env:
MM_API_KEY: ${{ secrets.MM_API_KEY }}
run: |
curl -s https://api.multimail.dev/v1/list_pending \
-H "Authorization: Bearer $MM_API_KEY" | jq &"cm">#039;.status'Insert a `send_email` step after the job step you want to report on. Use `if: success()` or `if: failure()` to control when the notification fires. Include `github.sha`, `github.actor`, and `github.run_id` in the message body for traceability.
- name: Notify on failure
if: failure()
env:
MM_API_KEY: ${{ secrets.MM_API_KEY }}
run: |
curl -s -X POST https://api.multimail.dev/v1/send_email \
-H "Authorization: Bearer $MM_API_KEY" \
-H "Content-Type: application/json" \
-d &"cm">#039;{
"from": "[email protected]",
"to": ["[email protected]"],
"subject": "Build failed: ${{ github.repository }}",
"text": "Workflow ${{ github.workflow }} failed.\nActor: ${{ github.actor }}\nRun: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}&"cm">#039;If using `gated_send`, log into the MultiMail dashboard to approve the first test sends and confirm the message content looks correct. Once satisfied with the format, switch the mailbox to `monitored` or `autonomous` if human approval per-send is not required for this workflow.
"cm"># Check pending messages from the mailbox
curl -s https://api.multimail.dev/v1/list_pending \
-H "Authorization: Bearer $MM_API_KEY" | jq &"cm">#039;.pending[] | {id, subject, created_at, status}'Email infrastructure built for AI agents. Verifiable identity, graduated oversight, and a 38-tool MCP server. Formally verified in Lean 4.