Email oversight for AI agents built on Salesforce

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.

Built for Salesforce developers

Approval gates on CRM-triggered email

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.

Identity verification across mailboxes

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.

Compliance audit trail per message

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.

Cancellable sends before delivery

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.

Inbound routing back into Salesforce

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.


Get started in minutes

Apex callout: submit a case escalation email for approval
apex
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.

Python agent: process Salesforce opportunity events and queue approval emails
python
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.

Webhook handler: write approval decisions back to Salesforce
python
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.

Cancel a pending message before approval
python
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.


Step by step

1

Create a MultiMail account and provision a mailbox

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.

2

Store your API key securely in Salesforce

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}.

bash
// Reference in Apex:
String apiKey = System.label.MultiMail_API_Key;
3

Set oversight mode for your first flow

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.

bash
// In your JSON payload to https://api.multimail.dev/v1/send:
{
  "oversight_mode": "gated_send"
}
4

Configure a webhook endpoint to receive approval events

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.

bash
"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"}'
5

Test end-to-end with a sandbox Salesforce org

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.


Common questions

Does MultiMail require installing a managed package in Salesforce?
No. MultiMail is a REST API. You call it from Apex via HttpRequest, from a Flow's HTTP callout action, or from an external agent process that consumes Salesforce events. There is no AppExchange package or Salesforce-side component required.
Can I use MultiMail with Salesforce Einstein AI or Agentforce?
Yes. Einstein and Agentforce can generate email drafts or determine when to reach out. You route those outputs through MultiMail's API rather than Salesforce's native email actions. MultiMail adds the approval gate, identity verification, and audit log that CRM-native tooling does not provide for AI-generated messages.
How do I match a MultiMail approval event back to the Salesforce record that triggered it?
Pass Salesforce record IDs in the metadata field when calling the send endpoint—for example, {"salesforce_case_id": "5003000000D8cuI"}. MultiMail echoes this metadata in every webhook event. Your webhook handler reads the case or opportunity ID from metadata and calls the Salesforce REST API to update the record.
What oversight mode should I use for high-volume Salesforce outreach like renewal reminders?
Start with gated_send during rollout so your team can validate the agent's output. Once output quality is confirmed over several hundred messages, move to monitored—the agent sends autonomously, but every message is logged and your team receives notifications. Reserve autonomous for low-risk, high-volume flows where your team has explicitly signed off on the agent's judgment.
How does MultiMail help with GDPR compliance for Salesforce-triggered emails?
GDPR Article 22 requires that automated decision-making affecting individuals be subject to human oversight on request. MultiMail's approval audit log records agent identity, timestamp, oversight mode, and the human reviewer's decision for every message. This log is queryable via the API and can be exported for regulatory documentation. For suppression and unsubscribe compliance, store opt-out status in Salesforce and check it before calling the MultiMail send endpoint.
Can inbound customer replies be routed back into Salesforce?
Yes. Configure a MultiMail inbound webhook to receive reply events. The payload includes the parsed email body, sender address, and the thread ID. Your webhook handler calls the Salesforce REST API to append the reply content to the relevant Case or Opportunity, or publishes a Platform Event that a Salesforce Flow picks up to create a follow-up task.
What happens if a Salesforce record changes after an email is queued for approval?
Call the cancel_message endpoint with the message ID before a reviewer approves it. MultiMail returns 200 if the cancellation succeeds or 409 if the message was already delivered. To automate this, listen for Salesforce Change Data Capture events on the relevant object and cancel the corresponding pending MultiMail message when the record enters a state that makes the queued email invalid.

Explore more

The only agent email with a verifiable sender

Email infrastructure built for AI agents. Verifiable identity, graduated oversight, and a 38-tool MCP server. Formally verified in Lean 4.