MultiMail adds a dedicated email API and human-approval layer to Zapier AI automations so agents can send, read, and reply without skipping review.
Zapier AI Actions lets you expose Zap automations as callable tools for AI assistants and agentic workflows. When one of those tools involves email — notifying a lead, replying to a support ticket, escalating an alert — the generic Zapier Gmail or Outlook action offers no audit trail, no gating, and no per-tenant policy control.
MultiMail fills that gap. You configure a Zap that calls MultiMail's REST API, expose it as a Zapier AI Action, and your assistant gains a send_email or reply_email tool that routes through MultiMail's oversight layer. Outbound messages queue for human approval under gated_send mode before any bytes leave the server.
Inbound email works the same way in reverse: MultiMail fires a webhook into a Zapier catch hook when mail arrives, giving the AI assistant structured data — sender, subject, body, thread ID — without exposing raw IMAP credentials to your automation stack.
Zapier's native email steps send immediately. MultiMail's gated_send mode holds outbound messages until a human approves via the dashboard or the decide_email API endpoint, with a full audit trail attached to every decision.
Different Zaps warrant different risk tolerance. A CRM follow-up Zap can run under monitored mode while a payment-dispute Zap stays on gated_all. Oversight mode is set per mailbox, not per run, so policies survive Zap edits.
MultiMail webhooks emit normalized JSON — thread_id, sender reputation, tag classifications — that Zapier filter and path steps can branch on directly, without a parsing code step.
If you run Zaps on behalf of multiple clients, each client gets their own MultiMail mailbox and API key. Email never crosses tenant boundaries, which matters for GDPR data-processor obligations.
MultiMail webhooks fire on delivery, bounce, and approval events. A Zap path step can branch on approval_granted versus approval_denied and continue automation accordingly, without polling.
const response = await fetch('https://api.multimail.dev/v1/send_email', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MM_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: '[email protected]',
to: inputData.contact_email,
subject: `Follow-up: ${inputData.deal_name}`,
body: inputData.generated_body,
oversight_mode: 'gated_send',
tags: ['crm', 'zapier-ai'],
metadata: {
zap_id: inputData.zap_id,
deal_id: inputData.deal_id,
},
}),
});
const result = await response.json();
if (!response.ok) {
throw new Error(`MultiMail error: ${result.error}`);
}
"cm">// Return message_id so downstream Zap steps can reference it
output = { message_id: result.message_id, status: result.status };A Zapier Code (JavaScript) step that calls the MultiMail REST API to queue an outbound email under gated_send oversight. The message ID returned can be stored in a Zapier storage step for later approval tracking.
import requests
def fetch_unread_via_zapier(zapier_ai_action_id: str, zapier_token: str, mailbox: str) -> dict:
"""Trigger a Zapier AI Action that proxies MultiMail check_inbox."""
resp = requests.post(
f"https://actions.zapier.com/api/v1/dynamic/exposed/{zapier_ai_action_id}/execute/",
headers={"X-API-Key": zapier_token},
json={
"instructions": f"Fetch unread emails from {mailbox}",
"mailbox": mailbox,
"limit": 10,
"filter": "unread",
},
timeout=15,
)
resp.raise_for_status()
data = resp.json()
"cm"># The Zap's MultiMail step returns structured messages
return data.get("results", [])
"cm"># Usage in agent loop
messages = fetch_unread_via_zapier(
zapier_ai_action_id="01JABCD123",
zapier_token="sk-zapier-...",
mailbox="[email protected]",
)
for msg in messages:
print(msg["subject"], msg["sender"], msg["thread_id"])Agent code that triggers a Zapier AI Action configured to call MultiMail's check_inbox endpoint. The action returns unread messages the agent can act on without holding raw IMAP credentials.
"cm">// Zapier Catch Hook receives MultiMail webhook events
"cm">// Configure webhook URL in MultiMail dashboard under Settings > Webhooks
const event = inputData.event; "cm">// 'approval.granted' | 'approval.denied'
const messageId = inputData.message_id;
const decisionNote = inputData.note; "cm">// Human reviewer's comment if any
if (event === 'approval.granted') {
// Message will be delivered by MultiMail automatically
// Continue Zap path: update CRM, notify Slack, etc.
output = {
action: 'continue',
message_id: messageId,
note: decisionNote || 'Approved',
};
} else if (event === 'approval.denied') {
// Log denial and halt downstream steps
output = {
action: 'halt',
message_id: messageId,
reason: decisionNote || 'Denied by reviewer',
};
} else {
// Unknown event — surface for debugging
output = { action: 'unknown', event };
}A Zapier Code step that processes MultiMail's approval.granted or approval.denied event and branches downstream automation — continuing the workflow on approval, logging denial with reason.
"cm">// Zapier Code step — parse MultiMail inbound webhook payload
"cm">// MultiMail fires this when mail arrives at your mailbox
const payload = JSON.parse(inputData.raw_body || '{}');
const email = {
message_id: payload.message_id,
thread_id: payload.thread_id,
from: payload.from?.address,
from_name: payload.from?.name,
subject: payload.subject,
body_text: payload.body?.text,
received_at: payload.received_at,
"cm">// Reputation fields MultiMail attaches automatically
sender_score: payload.sender?.reputation_score,
tags: (payload.tags || []).join(','),
};
"cm">// Fail loudly if required fields are missing
if (!email.message_id || !email.from) {
throw new Error('Invalid MultiMail webhook payload — missing message_id or from');
}
output = email;Configure MultiMail to POST inbound email events to a Zapier catch hook. This Zap path step parses the payload so downstream AI Actions receive clean fields.
"cm">// inputData.message_id and inputData.decision come from
"cm">// a preceding Zapier approval step or Slack interactive message handler
const { message_id, decision, reviewer_note } = inputData;
const response = await fetch(`https:"cm">//api.multimail.dev/v1/decide_email`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MM_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
message_id,
decision, "cm">// 'approve' | 'deny'
note: reviewer_note || '',
}),
});
const result = await response.json();
if (!response.ok) {
throw new Error(`decide_email failed: ${result.error}`);
}
output = { status: result.status, message_id: result.message_id };A Zapier Code step that calls MultiMail's decide_email endpoint after a human reviews the message in a Slack approval workflow or similar side-channel.
Sign up at multimail.dev and create a mailbox for your Zapier automation (e.g., [email protected]). Copy the mm_live_... API key from the dashboard — you will add this as a Zapier environment variable or secret in the next step.
curl -X POST https://api.multimail.dev/v1/create_mailbox \
-H "Authorization: Bearer $MULTIMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d &"cm">#039;{"address": "[email protected]", "oversight_mode": "gated_send"}'In Zapier, go to your account settings and add MM_API_KEY as an environment variable (Zapier Code steps can read process.env values). For team Zaps, use Zapier's Secret Manager so the key is not stored in plain text inside Zap steps.
Create a new Zap with a Webhook or AI Action trigger. Add a Code (JavaScript) step that calls POST https://api.multimail.dev/v1/send_email using the API key from process.env.MM_API_KEY. Map the trigger fields (to, subject, body) into the request body. Set oversight_mode to gated_send.
// Zap Code step — send via MultiMail
const res = await fetch(&"cm">#039;https://api.multimail.dev/v1/send_email', {
method: &"cm">#039;POST',
headers: {
&"cm">#039;Authorization': `Bearer ${process.env.MM_API_KEY}`,
&"cm">#039;Content-Type': 'application/json',
},
body: JSON.stringify({
from: &"cm">#039;[email protected]',
to: inputData.recipient,
subject: inputData.subject,
body: inputData.body,
oversight_mode: &"cm">#039;gated_send',
}),
});
const data = await res.json();
output = { message_id: data.message_id };In the Zapier AI Actions dashboard, expose this Zap as a callable tool. Give it a clear name (e.g., 'Send CRM follow-up email') and description so the AI assistant can select it correctly. The action takes recipient, subject, and body as inputs and returns message_id.
In the MultiMail dashboard under Settings > Webhooks, add your second Zapier catch hook URL as the approval event endpoint. This Zap receives approval.granted and approval.denied events so you can update downstream systems (CRM records, Slack threads) without polling.
curl -X POST https://api.multimail.dev/v1/webhooks \
-H "Authorization: Bearer $MULTIMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d &"cm">#039;{"url": "https://hooks.zapier.com/hooks/catch/YOUR_HOOK_ID/", "events": ["approval.granted", "approval.denied", "message.delivered"]}'Email infrastructure built for AI agents. Verifiable identity, graduated oversight, and a 38-tool MCP server. Formally verified in Lean 4.