AI agents read Notion pages and databases to build context, then send or route email through MultiMail with the right oversight mode applied.
Notion serves as the knowledge layer for many AI agent workflows — task lists, CRM records, project wikis, and routing policies all live there. When an agent needs to act on that knowledge by sending email, it needs a delivery API that can handle the gap between "I read the context" and "I sent something I shouldn't have."
MultiMail sits at the delivery end of that pipeline. An agent queries a Notion database to find open action items, reads the relevant pages for context, then calls MultiMail's send_email or decide_email endpoints with the appropriate oversight mode applied. Notion informs the draft; MultiMail decides whether and how it ships.
This pattern appears in outreach automation, customer success tooling, and internal ops agents. The agent pulls structured data — assignee, deadline, status — from a Notion database, constructs a tailored message, and hands off to MultiMail, which can gate the send on human approval, run monitored, or operate fully autonomously depending on how much trust the workflow has earned.
Notion provides the signal for what to write. MultiMail's gated_send mode ensures that context-informed drafts still require human sign-off before delivery. The two layers are independent: adjust the Notion query logic without touching the oversight policy, or tighten oversight without changing what the agent reads.
Structured Notion properties — assignee email, due date, priority tag — map cleanly to MultiMail API parameters. An agent can read a database row and call send_email without an intermediate transformation step.
Start with gated_all while your agent is learning the workflow. Promote to gated_send once sends look correct. Run monitored or autonomous once you trust the output. oversight_mode is a per-request parameter, not a system-wide config change.
When an agent reads a Notion routing table to determine how to handle inbound email, MultiMail's decide_email endpoint executes that decision — archive, tag, forward, or reply — without requiring a separate delivery call.
MultiMail fires webhooks on inbound email, approval events, and delivery status. Agents can use these to update Notion database rows — logging message IDs, changing status properties, or creating follow-up tasks — so your workspace stays in sync with your email queue.
import { Client } from '@notionhq/client';
const notion = new Client({ auth: process.env.NOTION_TOKEN });
async function sendOutreachFromNotion(databaseId) {
const response = await notion.databases.query({
database_id: databaseId,
filter: {
property: 'Status',
select: { equals: 'Ready to Contact' }
}
});
for (const page of response.results) {
const name = page.properties.Name.title[0]?.plain_text;
const email = page.properties.Email.email;
const notes = page.properties.Notes.rich_text[0]?.plain_text ?? '';
if (!email) continue;
const res = await fetch('https://api.multimail.dev/v1/send_email', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MULTIMAIL_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
from: '[email protected]',
to: email,
subject: `Following up — ${name}`,
text: `Hi ${name},\n\n${notes}\n\nLet me know if you have questions.`,
oversight_mode: 'gated_send',
metadata: { notion_page_id: page.id }
})
});
const { message_id, status } = await res.json();
console.log(`${message_id}: ${status}`);
"cm">// status = 'pending_approval' — approval webhook fires when human decides
}
}Query a Notion database for contacts with a specific status, then send a tailored email via MultiMail for each result. Uses gated_send so each send requires human approval before delivery.
import { Client } from '@notionhq/client';
const notion = new Client({ auth: process.env.NOTION_TOKEN });
async function draftStatusUpdate(pageId, recipientEmail) {
const [pageResp, blocksResp] = await Promise.all([
notion.pages.retrieve({ page_id: pageId }),
notion.blocks.children.list({ block_id: pageId })
]);
const title = pageResp.properties.title?.title[0]?.plain_text ?? 'Project Update';
const body = blocksResp.results
.filter(b => b.type === 'paragraph')
.map(b => b.paragraph.rich_text.map(t => t.plain_text).join(''))
.filter(Boolean)
.join('\n\n');
const res = await fetch('https://api.multimail.dev/v1/send_email', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MULTIMAIL_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
from: '[email protected]',
to: recipientEmail,
subject: `Status: ${title}`,
text: `Project: ${title}\n\n${body}`,
oversight_mode: 'gated_all',
metadata: { notion_page_id: pageId }
})
});
const { message_id, status } = await res.json();
console.log(`${message_id}: ${status}`);
}Fetch a Notion project page, extract paragraph blocks as body text, then submit a status update email under gated_all mode. Reviewers see the exact Notion content before approving.
import { Client } from '@notionhq/client';
const notion = new Client({ auth: process.env.NOTION_TOKEN });
"cm">// Called from your MultiMail inbound webhook handler
export async function handleInbound(webhookPayload) {
const { email_id, from } = webhookPayload;
const senderDomain = from.split('@')[1];
const routing = await notion.databases.query({
database_id: process.env.ROUTING_DATABASE_ID,
filter: {
property: 'Domain Pattern',
rich_text: { contains: senderDomain }
}
});
let decision = 'archive';
let assignTo = null;
if (routing.results.length > 0) {
const rule = routing.results[0];
decision = rule.properties.Action.select?.name ?? 'archive';
assignTo = rule.properties.AssignTo.email ?? null;
}
await fetch('https://api.multimail.dev/v1/decide_email', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MULTIMAIL_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
email_id,
decision,
assign_to: assignTo,
reason: `Matched Notion routing rule for domain ${senderDomain}`
})
});
}On inbound email, query a Notion database that maps sender domains to routing actions, then call MultiMail's decide_email endpoint to execute the result. The Notion table is a human-editable policy your agent reads at runtime.
import { Client } from '@notionhq/client';
const notion = new Client({ auth: process.env.NOTION_TOKEN });
export async function onApprovalEvent(event) {
if (event.type !== 'message.approved') return;
const { message_id, metadata } = event;
"cm">// Tag the email in MultiMail
await fetch('https://api.multimail.dev/v1/tag_email', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.MULTIMAIL_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
email_id: message_id,
tags: ['sent-approved', 'notion-tracked']
})
});
"cm">// Write outcome back to Notion if we have a page ID in metadata
if (metadata?.notion_page_id) {
await notion.pages.update({
page_id: metadata.notion_page_id,
properties: {
Status: { select: { name: 'Email Sent' } },
MessageId: { rich_text: [{ text: { content: message_id } }] },
SentAt: { date: { start: new Date().toISOString() } }
}
});
}
}When MultiMail fires a message.approved webhook, tag the email and write the outcome back to the originating Notion page. Keeps your workspace in sync with what actually shipped.
Install the official Notion JavaScript client and create an internal integration at developers.notion.com. Share the relevant databases with that integration, then set your tokens as environment variables.
npm install @notionhq/client
export NOTION_TOKEN=secret_...
export MULTIMAIL_API_KEY=mm_live_...Give your agent a stable send address using MultiMail's create_mailbox endpoint. Set oversight_mode to gated_send for first-run safety — you can relax it once you've reviewed a batch of drafts.
curl -X POST https://api.multimail.dev/v1/create_mailbox \
-H &"cm">#039;Authorization: Bearer $MULTIMAIL_API_KEY...' \
-H &"cm">#039;Content-Type: application/json' \
-d &"cm">#039;{
"address": "[email protected]",
"display_name": "Notion Workflow Agent",
"oversight_mode": "gated_send"
}&"cm">#039;Pull a record from your Notion database and pass the relevant fields to MultiMail's send_email endpoint. The message will land in pending_approval state until a human approves it.
import { Client } from &"cm">#039;@notionhq/client';
const notion = new Client({ auth: process.env.NOTION_TOKEN });
const { results } = await notion.databases.query({
database_id: &"cm">#039;YOUR_DATABASE_ID',
filter: { property: &"cm">#039;Status', select: { equals: 'Action Required' } }
});
const page = results[0];
const email = page.properties.Email.email;
const name = page.properties.Name.title[0]?.plain_text;
const res = await fetch(&"cm">#039;https://api.multimail.dev/v1/send_email', {
method: &"cm">#039;POST',
headers: {
&"cm">#039;Authorization': `Bearer ${process.env.MULTIMAIL_API_KEY}`,
&"cm">#039;Content-Type': 'application/json'
},
body: JSON.stringify({
from: &"cm">#039;[email protected]',
to: email,
subject: `Action required: ${name}`,
text: `Hi ${name}, this is an automated follow-up from your project workspace.`,
oversight_mode: &"cm">#039;gated_send',
metadata: { notion_page_id: page.id }
})
});
console.log(await res.json());Subscribe to MultiMail approval and delivery events so your agent can react to outcomes — updating Notion records, creating follow-up tasks, or escalating rejections.
curl -X POST https://api.multimail.dev/v1/webhooks \
-H &"cm">#039;Authorization: Bearer $MULTIMAIL_API_KEY...' \
-H &"cm">#039;Content-Type: application/json' \
-d &"cm">#039;{
"url": "https://your-worker.yourdomain.workers.dev/webhooks/multimail",
"events": ["message.approved", "message.rejected", "message.delivered", "email.inbound"]
}&"cm">#039;Email infrastructure built for AI agents. Verifiable identity, graduated oversight, and a 38-tool MCP server. Formally verified in Lean 4.