When agents react to Salesforce triggers—deal stage changes, case escalations, renewal alerts—MultiMail ensures every outbound email passes the right approval gate before it reaches your customer.
Salesforce Flows, Apex triggers, and Einstein AI can detect the right moment to reach out to a customer. What they cannot do is safely hand that email to an AI agent without human review. CRM-native email tooling has no concept of graduated autonomy—it either sends or it doesn't.
MultiMail sits between your Salesforce automation and the outbound SMTP layer. Agents receive CRM event data via webhook or direct API call, compose a response, and submit it through MultiMail's oversight pipeline. A gated_send configuration holds the message in a pending queue until a human approves it in the MultiMail dashboard or via a webhook callback.
This matters most in regulated industries. Salesforce is widely deployed in financial services, healthcare, and insurance—sectors where FINRA, HIPAA, and GDPR impose strict controls on AI-generated customer communications. MultiMail's approval audit log gives compliance teams a per-message record of what was sent, when, and by which agent identity.
The integration requires no Salesforce package installation. You call the MultiMail REST API from an Apex class, a Flow's HTTP callout action, or an external agent process that consumes Salesforce event data via Platform Events or Change Data Capture.
Salesforce can fire a trigger the moment a deal closes or a case breaches SLA. MultiMail's gated_send mode intercepts the agent's outbound email and holds it for human review before delivery—preventing premature or incorrect customer communications from shipping automatically.
Agents operating across multiple Salesforce orgs or business units can be bound to specific MultiMail mailboxes. Each mailbox has a verified sending identity, so customer-facing email always comes from the correct domain and passes DKIM and SPF checks—not from a shared alias or unverified relay.
Every message processed by MultiMail—whether approved, rejected, or cancelled—is logged with agent identity, timestamp, oversight mode, and approval decision. This satisfies GDPR Article 22 documentation requirements and FINRA record-keeping rules for AI-assisted customer outreach.
If an agent dispatches an email and a human reviewer catches an error, the cancel_message endpoint can revoke delivery before it leaves the queue. Salesforce's native email actions have no equivalent recall mechanism once triggered.
MultiMail webhooks deliver inbound email payloads to any HTTPS endpoint, including Salesforce Platform Event ingestion endpoints or external agent processes. Customer replies to agent-sent emails can be parsed, classified, and written back to the relevant Case or Opportunity record.
public class CaseEscalationEmailer {
@future(callout=true)
public static void sendEscalationEmail(String caseId, String contactEmail, String subject, String body) {
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.multimail.dev/v1/send');
req.setMethod('POST');
req.setHeader('Authorization', 'Bearer ' + System.label.MultiMail_API_Key);
req.setHeader('Content-Type', 'application/json');
Map<String, Object> payload = new Map<String, Object>{
'from' => '[email protected]',
'to' => contactEmail,
'subject' => subject,
'text' => body,
'oversight_mode' => 'gated_send',
'metadata' => new Map<String, String>{
'salesforce_case_id' => caseId,
'source' => 'apex_escalation_trigger'
}
};
req.setBody(JSON.serialize(payload));
Http http = new Http();
HttpResponse res = http.send(req);
if (res.getStatusCode() != 202) {
System.debug('MultiMail error: ' + res.getBody());
}
}
}An Apex trigger on the Case object fires when Priority changes to 'Critical'. It calls the MultiMail API with gated_send oversight so a human must approve before the email reaches the customer.
import requests
MULTIMAIL_API_KEY = "mm_live_your_key_here"
BASE_URL = "https://api.multimail.dev/v1"
HEADERS = {"Authorization": f"Bearer {MULTIMAIL_API_KEY}", "Content-Type": "application/json"}
def handle_opportunity_closed_won(opportunity: dict):
contact_email = opportunity["contact_email"]
account_name = opportunity["account_name"]
rep_name = opportunity["owner_name"]
body = (
f"Hi {account_name} team,\n\n"
f"Your contract has been processed. {rep_name} will follow up within one business day "
f"with onboarding details.\n\nThanks,\nAcme Sales Operations"
)
response = requests.post(
f"{BASE_URL}/send",
headers=HEADERS,
json={
"from": "[email protected]",
"to": contact_email,
"subject": f"Welcome aboard, {account_name}",
"text": body,
"oversight_mode": "gated_send",
"metadata": {
"opportunity_id": opportunity["id"],
"source": "crm_closed_won_agent"
}
}
)
response.raise_for_status()
message_id = response.json()["id"]
print(f"Queued for approval: {message_id}")
return message_id
def list_pending_approvals():
response = requests.get(f"{BASE_URL}/pending", headers=HEADERS)
response.raise_for_status()
return response.json()["messages"]An external Python agent subscribes to Salesforce Change Data Capture events for Opportunity stage changes, composes a renewal email, and submits it to MultiMail with gated_send. The agent then polls list_pending to surface queued messages for review.
from flask import Flask, request, jsonify
import requests
app = Flask(__name__)
SF_INSTANCE_URL = "https://acme.my.salesforce.com"
SF_ACCESS_TOKEN = "your_sf_access_token"
@app.route("/webhooks/multimail", methods=["POST"])
def multimail_webhook():
event = request.json
event_type = event.get("type")
message = event.get("message", {})
metadata = message.get("metadata", {})
case_id = metadata.get("salesforce_case_id")
if not case_id:
return jsonify({"ok": True})
if event_type == "message.approved":
status_note = "Escalation email approved and sent by MultiMail agent."
elif event_type == "message.rejected":
status_note = "Escalation email rejected in MultiMail approval queue."
else:
return jsonify({"ok": True})
sf_response = requests.patch(
f"{SF_INSTANCE_URL}/services/data/v58.0/sobjects/Case/{case_id}",
headers={
"Authorization": f"Bearer {SF_ACCESS_TOKEN}",
"Content-Type": "application/json"
},
json={"Description": status_note}
)
sf_response.raise_for_status()
return jsonify({"ok": True})
if __name__ == "__main__":
app.run(port=8080)MultiMail fires a webhook when a pending message is approved or rejected. This handler receives the event and uses the stored Salesforce Case ID from metadata to update the Case record via the Salesforce REST API.
import requests
MULTIMAIL_API_KEY = "mm_live_your_key_here"
BASE_URL = "https://api.multimail.dev/v1"
HEADERS = {"Authorization": f"Bearer {MULTIMAIL_API_KEY}"}
def cancel_pending_email(message_id: str, reason: str) -> bool:
response = requests.post(
f"{BASE_URL}/cancel/{message_id}",
headers={**HEADERS, "Content-Type": "application/json"},
json={"reason": reason}
)
if response.status_code == 200:
print(f"Cancelled {message_id}: {reason}")
return True
elif response.status_code == 409:
print(f"Message {message_id} already delivered, cannot cancel")
return False
response.raise_for_status()If a Salesforce record update invalidates a queued email—for example, the contact's account is put on hold—the agent can cancel the pending message using its ID before a reviewer approves it.
Sign up at multimail.dev and create a mailbox for your Salesforce-connected agent. Use a subdomain of your company domain (e.g., [email protected]) or verify your own domain to send from [email protected]. Copy your API key from the dashboard—it starts with mm_live_ for production or mm_test_ for sandbox testing.
In Salesforce Setup, create a Custom Label named MultiMail_API_Key and paste your mm_live_ key as the value. Custom Labels are org-level constants that Apex can read at runtime without hardcoding credentials. For Named Credentials, configure an external endpoint pointing to https://api.multimail.dev with the Authorization header set to Bearer {your_key}.
// Reference in Apex:
String apiKey = System.label.MultiMail_API_Key;When calling the MultiMail send endpoint, set oversight_mode to gated_send for any customer-facing email that needs human review. Use monitored if you want autonomous sends with a notification trail, or autonomous only after your team has validated the agent's output quality. Start with gated_send during development.
// In your JSON payload to https://api.multimail.dev/v1/send:
{
"oversight_mode": "gated_send"
}In the MultiMail dashboard under Settings > Webhooks, add your endpoint URL (e.g., https://your-agent-host.com/webhooks/multimail). Select the message.approved, message.rejected, and email.received event types. Your handler should read the metadata field to match each event back to the originating Salesforce record.
"cm"># Verify the webhook is receiving events:
curl -X POST https://api.multimail.dev/v1/webhooks/test \
-H "Authorization: Bearer $MULTIMAIL_API_KEY" \
-H "Content-Type: application/json" \
-d &"cm">#039;{"endpoint": "https://your-agent-host.com/webhooks/multimail"}'Use a Salesforce Developer Edition or sandbox org paired with a MultiMail mm_test_ key. Trigger your Flow or Apex callout manually, confirm the message appears in list_pending, approve it from the dashboard, and verify your webhook handler receives the message.approved event and writes the outcome back to the Salesforce record.
Email infrastructure built for AI agents. Verifiable identity, graduated oversight, and a 38-tool MCP server. Formally verified in Lean 4.