Paulo CastellanoHow to let AI agents send email
Give your AI agents the ability to compose and send email programmatically using the Sendkit API.

AI agents that can't send email are half-built. They can reason, search, summarize, and plan — but the moment they need to notify a customer, follow up on a lead, or confirm an appointment, they hit a wall. Email is still the default communication channel for business, and your agents need direct access to it.
This guide shows you how to wire up AI agents to send email through the Sendkit API, with working code in Node.js and Python, safety guardrails, and webhook integration for closed-loop workflows.
Why AI agents need email
The use cases are everywhere once you look:
- Customer support agents resolve a ticket and send a summary to the customer. No human copies and pastes a template.
- Sales agents qualify inbound leads and send personalized follow-ups within seconds of a form submission.
- Scheduling agents negotiate meeting times over email, sending confirmations and calendar invites automatically.
- Notification agents monitor systems and alert stakeholders when something breaks, with context the LLM assembled from logs.
In each case, the agent generates the content. Your code validates it. Sendkit delivers it. That separation matters.
The architecture
Never let an LLM call an email API directly with no oversight. The pattern that works:
- LLM generates content — subject line, body, recipient selection
- Your code validates — checks recipient against allow-lists, scans content for policy violations, enforces rate limits
- Sendkit sends — delivers the email and returns structured status
This three-step flow keeps the agent useful without making it dangerous. The LLM never holds API credentials directly. Your application layer is the gatekeeper.

The tool/function calling pattern
Modern LLMs support tool calling (OpenAI calls them "functions", Anthropic calls them "tools"). You define a send_email tool with a schema, and the model invokes it when it decides an email should go out. Your code intercepts that call, validates it, and executes the actual send.
Here's the tool definition you'd give to an LLM:
{
"name": "send_email",
"description": "Send an email to a recipient. Use this when the user needs to be notified via email or when a follow-up email is required.",
"parameters": {
"type": "object",
"properties": {
"to": { "type": "string", "description": "Recipient email address" },
"subject": { "type": "string", "description": "Email subject line" },
"html": { "type": "string", "description": "Email body as HTML" }
},
"required": ["to", "subject", "html"]
}
}The LLM never sees your API key. It just fills in to, subject, and html. Your code handles the rest.
Node.js: agent tool that sends email via Sendkit
Install the SDK:
npm install @sendkitdev/sdkDefine the tool handler:
import { Sendkit } from '@sendkitdev/sdk';
const sendkit = new Sendkit('sk_live_your_api_key');
const APPROVED_SENDERS = ['[email protected]', '[email protected]'];
const RATE_LIMIT = new Map(); // email -> timestamp[]
const handleSendEmailTool = async params => {
const { to, subject, html } = params;
const from = '[email protected]';
// Validate sender
if (!APPROVED_SENDERS.includes(from)) {
return { error: 'Sender not in approved list' };
}
// Rate limit: max 5 emails per recipient per hour
const now = Date.now();
const history = RATE_LIMIT.get(to) || [];
const recent = history.filter(ts => now - ts < 3600000);
if (recent.length >= 5) {
return { error: 'Rate limit exceeded for this recipient' };
}
// Send via Sendkit
const { data, error } = await sendkit.emails.send({
from,
to,
subject,
html,
});
if (error) {
return { error: error.message };
}
// Track for rate limiting
recent.push(now);
RATE_LIMIT.set(to, recent);
return { success: true, messageId: data.id };
};Wire this into your LLM loop. When the model returns a tool call with name: "send_email", parse the arguments and pass them to handleSendEmailTool. Return the result back to the model so it can confirm delivery to the user.
For a deeper dive on Node.js email sending, see How to send transactional email with Node.js.
Python: agent tool that sends email via Sendkit
Install the SDK:
pip install sendkitDefine the tool handler:
from sendkit import Sendkit
from time import time
client = Sendkit("sk_live_your_api_key")
APPROVED_SENDERS = ["[email protected]", "[email protected]"]
rate_limit: dict[str, list[float]] = {}
def handle_send_email_tool(params: dict) -> dict:
to = params["to"]
subject = params["subject"]
html = params["html"]
from_ = "[email protected]"
if from_ not in APPROVED_SENDERS:
return {"error": "Sender not in approved list"}
# Rate limit: max 5 emails per recipient per hour
now = time()
history = rate_limit.get(to, [])
recent = [ts for ts in history if now - ts < 3600]
if len(recent) >= 5:
return {"error": "Rate limit exceeded for this recipient"}
result = client.emails.send(
from_=from_,
to=to,
subject=subject,
html=html,
)
if result.error:
return {"error": result.error.message}
recent.append(now)
rate_limit[to] = recent
return {"success": True, "message_id": result.data.id}For more on Python email sending, see How to send email with Python.
Validate recipients before sending
An AI agent that sends to invalid addresses burns your sender reputation. Before every send, validate the recipient using Sendkit's email validation API:
const validateAndSend = async params => {
const { to, subject, html } = params;
// Validate recipient first
const { data: validation } = await sendkit.emailValidations.validate({
email: to,
});
if (validation.result === 'undeliverable') {
return { error: `Invalid recipient: ${to}` };
}
if (validation.is_disposable) {
return { error: 'Disposable email addresses are not allowed' };
}
// Proceed with send
return handleSendEmailTool(params);
};This catches typos, dead mailboxes, and disposable addresses before they hit your sending infrastructure. Your bounce rate stays low, and your domain reputation stays intact. Read more about this in How to handle email bounces.

Safety guardrails
Giving an LLM the ability to send email on your behalf requires serious guardrails. Here's what to implement:
Approved sender and recipient lists. Restrict the from address to verified senders. For high-risk agents, maintain an allow-list of recipient domains too.
Rate limiting. Both per-recipient and global. An agent stuck in a loop can spam a mailbox in seconds. The code examples above show per-recipient limits — add a global counter as well.
Content review. Scan outgoing messages for sensitive data (SSNs, credit card numbers, internal URLs). A regex pass catches the obvious cases. For stricter environments, run a second LLM call to audit the content.
Human-in-the-loop. For sensitive emails — legal notices, financial disclosures, anything to a VIP list — queue the email for human approval instead of sending immediately. The agent drafts, a human approves, then your code sends.
Logging. Log every email the agent sends, including the full prompt context that led to the send. You need an audit trail.
Webhook integration
Sending is half the loop. Your agent also needs to react to what happens after delivery. Sendkit fires webhooks for delivery events, bounces, opens, clicks, and complaints.
import express from 'express';
const app = express();
app.use(express.json());
app.post('/webhooks/sendkit', (req, res) => {
const event = req.body;
switch (event.type) {
case 'email.bounced':
// Feed back to agent: this recipient is invalid
// Update your allow-list or CRM
break;
case 'email.delivered':
// Mark conversation as "email sent successfully"
break;
case 'email.complained':
// Block future sends to this recipient
// Alert your team
break;
}
res.sendStatus(200);
});Wire bounce events back into your agent's context. If a recipient bounces, the agent should know not to retry that address. This creates a feedback loop that makes the agent smarter over time.
Why API beats SMTP for agents
SMTP is a conversation protocol. Your code opens a socket, negotiates TLS, authenticates, sends MAIL FROM, RCPT TO, DATA, waits for response codes. It's fragile, slow, and gives you almost no structured feedback.
An API call is a single HTTP request. You get back JSON with a message ID, or a structured error with a code you can branch on. For an AI agent that might send dozens of emails in a session, the difference is massive:
- Speed — one HTTP round-trip vs. a multi-step SMTP handshake
- Error handling — typed error objects vs. parsing SMTP reply codes
- Observability — every send is logged with metadata, queryable via the API
- No connection management — no persistent sockets, no pooling, no timeout drama
If you want a detailed comparison, read SMTP vs Email API.
Putting it all together
The stack is straightforward: your LLM generates email content through tool calling, your application layer validates and rate-limits, Sendkit handles delivery and gives you structured feedback through webhooks. The agent stays useful without being dangerous.
Start with the Sendkit API reference to get your keys and verify a domain. Check pricing — the free tier is generous enough to build and test your agent before you scale.
The agents that win are the ones that can actually do things. Sending email is one of the most valuable actions you can unlock.
Share this article