Store contact records, sync email interaction history, and attach custom metadata — so agents personalize outreach without building pipelines across five systems.
AI agents that send email often work blind. Contact data lives in CRMs, product databases, and support tickets — none of which the agent can query at send time without brittle integrations. The result: generic messages, missed context, and repeated asks for information the agent should already have. Scattered identity data also creates GDPR compliance risk: a right-to-erasure request requires coordinated deletes across every system that touched the contact.
MultiMail ties contact records directly to email activity. When an agent reads or sends a message, interaction history updates automatically. Custom metadata — plan tier, last feature used, open support tickets — travels with the contact record, so every agent that touches that mailbox has full context without building ETL pipelines. GDPR deletion propagates in a single API call.
POST a contact with email address, name, and any custom metadata fields. Existing records are updated on email match; new records are created on miss. Metadata is arbitrary JSON — no schema migration required.
Every send_email, reply_email, or inbound webhook event is appended to the matching contact's interaction log. Agents query this log to see what was sent, when, and whether the recipient replied — without touching your CRM.
Use manage_contacts to find contacts by email, domain, metadata field, or interaction recency before composing a message. Agents avoid duplicate outreach and can branch logic based on prior thread context.
At send time, the agent reads the contact record to pull metadata — product tier, last login, open items — and constructs a message grounded in actual account state rather than templated assumptions.
After a send or reply, the agent writes the new state back to the contact record: last contacted date, campaign stage, custom flags. Downstream agents pick up the updated state without re-fetching from external systems.
import multimail
client = multimail.Client(api_key="mm_live_...")
contact = client.contacts.upsert(
email="[email protected]",
name="Elena Vasquez",
metadata={
"plan": "pro",
"last_login": "2026-04-17",
"open_tickets": 2,
"product_interest": ["analytics", "export"]
}
)
print(contact.id) "cm"># contact_01HV...Create or update a contact record with structured metadata your agents can read back at send time.
import multimail
client = multimail.Client(api_key="mm_live_...")
"cm"># Find pro-plan contacts not reached in 30+ days
results = client.contacts.search(
domain="acmecorp.com",
filters={
"last_contacted_before": "2026-03-20",
"metadata.plan": "pro"
},
limit=50
)
for contact in results.items:
print(contact.email, contact.metadata.get("last_login"))Find contacts by domain and metadata field so the agent checks prior history before any outreach run.
import multimail
client = multimail.Client(api_key="mm_live_...")
contact = client.contacts.get(email="[email protected]")
meta = contact.metadata
first_name = contact.name.split()[0]
if meta.get("open_tickets", 0) > 0:
subject = "Following up on your open items"
body = (
f"Hi {first_name}, I noticed you have {meta[&"cm">#039;open_tickets']} open "
f"support items. Based on your interest in "
f"{&"cm">#039;, '.join(meta['product_interest'])}, here's the next best step..."
)
else:
subject = f"What&"cm">#039;s next for your {meta['plan']} account"
body = f"Hi {first_name}, based on your recent activity..."
client.send_email(
from_mailbox="[email protected]",
to=contact.email,
subject=subject,
body=body,
oversight_mode="monitored"
)Read a contact record, build a message grounded in account state, and send via monitored oversight.
import multimail
from datetime import date
client = multimail.Client(api_key="mm_live_...")
client.contacts.update(
email="[email protected]",
metadata={
"last_contacted": date.today().isoformat(),
"campaign_stage": "follow_up_sent",
"follow_up_due": "2026-04-26"
}
)Write the interaction outcome back to the contact record so the next agent has current state without re-fetching from upstream systems.
curl -X POST https://api.multimail.dev/contacts \
-H "Authorization: Bearer $MULTIMAIL_API_KEY..." \
-H "Content-Type: application/json" \
-d &"cm">#039;{
"email": "[email protected]",
"name": "Elena Vasquez",
"upsert": true,
"metadata": {
"plan": "pro",
"last_login": "2026-04-17",
"product_interest": ["analytics", "export"]
}
}&"cm">#039;Direct HTTP call to create or update a contact record from any runtime or language.
Tool: manage_contacts
Input:
query: "acmecorp.com"
filters:
metadata.plan: "pro"
last_contacted_before: "2026-03-20"
limit: 25
Returns: list of contact objects with email, name, metadata,
last_contacted, interaction_count, and latest_thread_idCall manage_contacts as an MCP tool from any MCP-compatible client to ground agent decisions in live contact state.
Contact metadata is arbitrary JSON. Store product tier, account flags, or agent-specific state without defining schemas in advance. New keys are merged on update; existing keys are only overwritten if included in the payload.
Every email sent or received through MultiMail updates the relevant contact's interaction log. Agents query recency and thread count without parsing raw message headers or writing their own logging layer.
Deleting a contact cascades to associated interaction logs and metadata. Right-to-erasure requests require one API call rather than coordinated deletes across your CRM, data warehouse, and email provider.
Because contact state lives in MultiMail rather than agent memory, a different agent or model version picks up the same context — no re-hydration prompts, no state reconstruction from raw email threads.
Before any outreach batch, agents call manage_contacts to check recency and confirm no active thread exists with the contact. This eliminates duplicate sends without maintaining a separate suppression list.
Email infrastructure built for AI agents. Verifiable identity, graduated oversight, and a 50-tool MCP server. Formally verified in Lean 4.