Paperclip is reshaping how developers think about autonomous AI systems. With over 26,000 GitHub stars and a rapidly growing community, it has become the go-to open-source framework for building "zero-human companies" — fully autonomous organizations where AI agents collaborate through org charts, ticketing systems, and governance protocols. But there's a gap that every Paperclip builder hits sooner or later: your agents can talk to each other, but they can't talk to the outside world. They can't email a customer, reply to a vendor, or send a status update to a stakeholder. This guide closes that gap.
Dead Simple Email gives each of your Paperclip agents its own email identity — a real inbox with a real address that can send and receive messages through a clean REST API. No SMTP configuration, no OAuth flows, no Google Workspace accounts to manage. In this guide, we'll build a complete Paperclip organization where Sales, Support, and Operations agents each have their own email addresses and coordinate multi-step workflows entirely through email.
Architecture Overview
Before we write any code, let's understand how Paperclip and Dead Simple Email fit together. Paperclip manages your agent org — who does what, how tasks route between agents, and what governance rules apply. Dead Simple Email handles the external communication layer — creating inboxes, sending messages, receiving inbound email via webhooks, and managing threads.
The integration point is straightforward. Each agent role in your Paperclip org maps to a Dead Simple inbox. Inbound emails arrive via webhooks and get converted into Paperclip tickets. Agents process those tickets and send replies through the Dead Simple API. The full flow looks like this:
- External email arrives at
sales@yourco.deadsimple.email - Dead Simple fires a webhook to your adapter service with the full message payload
- Your adapter creates a Paperclip ticket and assigns it to the Sales agent based on the inbox that received it
- The Sales agent processes the ticket, decides on a response, and marks the ticket with the reply content
- Your adapter sends the reply via the Dead Simple API from the same inbox, maintaining clean conversation threads
This architecture keeps concerns separated. Paperclip handles orchestration and decision-making. Dead Simple handles email delivery and reliability. Your adapter service is the thin glue layer between them.
Prerequisites
- Paperclip installed and running — follow the setup guide at paperclip.ing
- Dead Simple Email account — sign up free (5 inboxes included on the free tier)
- API key — generate one from your Dead Simple dashboard under Settings → API Keys
- Node.js 18+ — for the adapter service
- A public URL for webhook delivery (use ngrok or similar for local development)
Step 1: Plan Your Agent Email Architecture
The first decision is mapping agent roles to email inboxes. In Paperclip, each agent has a role, a set of capabilities, and a position in the org chart. We'll give each role its own email address so that external communication is clearly attributed.
For this guide, we'll build a three-agent org:
| Agent Role | Email Address | Responsibility |
|---|---|---|
| Sales Agent | sales@yourco.deadsimple.email |
Qualify inbound leads, respond to pricing inquiries, schedule demos |
| Support Agent | support@yourco.deadsimple.email |
Handle customer issues, onboard new users, resolve tickets |
| Ops Agent | ops@yourco.deadsimple.email |
Provision accounts, manage infrastructure alerts, coordinate with vendors |
Why separate inboxes instead of one shared mailbox? Three reasons. First, identity: when a customer replies to a sales email, it should go back to the sales agent, not into a generic queue. Second, accountability: you can audit exactly which agent sent which message. Third, routing: Dead Simple webhooks are per-inbox, so incoming email automatically routes to the right agent without any classification logic.
If your org has more roles — say, a Finance agent or an HR agent — just add more inboxes. Dead Simple's Pro plan supports 100 inboxes for $29/mo, and the Scale plan goes up to 500.
Step 2: Create Inboxes via the Dead Simple API
Let's create the inboxes. Set your API key as an environment variable first:
export DEADSIMPLE_API_KEY="dse_your_api_key_here"
Now create the Sales inbox:
curl -X POST https://api.deadsimple.email/v1/inboxes \ -H "Authorization: Bearer dse_..." \ -H "Content-Type: application/json" \ -d '{"name": "sales", "display_name": "Sales Agent"}'
Response:
{
"data": {
"inbox_id": "inb_s4l3s7x9k2",
"address": "sales@yourco.deadsimple.email",
"name": "sales",
"display_name": "Sales Agent",
"status": "active",
"created_at": "2026-03-26T10:00:00Z"
}
}
Repeat for Support and Ops:
curl -X POST https://api.deadsimple.email/v1/inboxes \
-H "Authorization: Bearer dse_..." \
-H "Content-Type: application/json" \
-d '{"name": "support", "display_name": "Support Agent"}'
curl -X POST https://api.deadsimple.email/v1/inboxes \
-H "Authorization: Bearer dse_..." \
-H "Content-Type: application/json" \
-d '{"name": "ops", "display_name": "Operations Agent"}'
Or create all three at once with the bulk endpoint:
curl -X POST https://api.deadsimple.email/v1/inboxes/bulk \ -H "Authorization: Bearer dse_..." \ -H "Content-Type: application/json" \ -d '{ "inboxes": [ {"name": "sales", "display_name": "Sales Agent"}, {"name": "support", "display_name": "Support Agent"}, {"name": "ops", "display_name": "Operations Agent"} ] }'
Verify everything is set up by listing your inboxes:
curl https://api.deadsimple.email/v1/inboxes \
-H "Authorization: Bearer dse_..."
You should see all three inboxes with "status": "active". Each one is immediately ready to send and receive email — no DNS propagation, no verification steps.
Step 3: Build an Email Adapter for Paperclip
The adapter is the bridge between Dead Simple's webhook system and Paperclip's ticket system. It's a small Express service that does two things: receives inbound email webhooks and converts them to Paperclip tickets, and sends outbound email when agents complete their tasks.
const express = require("express"); const crypto = require("crypto"); const app = express(); app.use(express.json()); // Configuration const DEADSIMPLE_API_KEY = process.env.DEADSIMPLE_API_KEY; const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; const PAPERCLIP_API_URL = process.env.PAPERCLIP_API_URL || "http://localhost:8080"; // Map inbox IDs to Paperclip agent roles const INBOX_TO_AGENT = { "inb_s4l3s7x9k2": "sales-agent", "inb_supp0rt3d": "support-agent", "inb_0ps4g3nt1": "ops-agent", }; // Verify HMAC-SHA256 webhook signature function verifySignature(payload, signature) { const expected = crypto .createHmac("sha256", WEBHOOK_SECRET) .update(payload) .digest("hex"); return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(signature) ); } // Send email via Dead Simple API async function sendEmail(inboxId, to, subject, body) { const res = await fetch( `https://api.deadsimple.email/v1/inboxes/${inboxId}/messages`, { method: "POST", headers: { "Authorization": `Bearer ${DEADSIMPLE_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ to, subject, body }), } ); return res.json(); } // Create a Paperclip ticket from an inbound email async function createPaperclipTicket(agentRole, emailData) { const res = await fetch(`${PAPERCLIP_API_URL}/api/tickets`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ assignee: agentRole, title: `Email: ${emailData.subject}`, type: "inbound-email", metadata: { from: emailData.from, to: emailData.to, subject: emailData.subject, body_text: emailData.body_text, message_id: emailData.id, inbox_id: emailData.inbox_id, }, }), }); return res.json(); } // Webhook endpoint — receives inbound emails from Dead Simple app.post("/webhook/email", async (req, res) => { // Verify signature const signature = req.headers["x-deadsimple-signature"]; if (!verifySignature(JSON.stringify(req.body), signature)) { return res.status(401).json({ error: "invalid signature" }); } const emailData = req.body.data; const agentRole = INBOX_TO_AGENT[emailData.inbox_id]; if (!agentRole) { console.warn(`No agent mapped for inbox ${emailData.inbox_id}`); return res.status(200).json({ status: "skipped" }); } // Create a Paperclip ticket for the appropriate agent const ticket = await createPaperclipTicket(agentRole, emailData); console.log(`Ticket created: ${ticket.id} for ${agentRole}`); res.status(200).json({ status: "ok", ticket_id: ticket.id }); }); // Callback endpoint — Paperclip calls this when an agent completes a task app.post("/callback/send-reply", async (req, res) => { const { inbox_id, to, subject, body } = req.body; const result = await sendEmail(inbox_id, to, subject, body); console.log(`Email sent: ${result.data.message_id}`); res.status(200).json(result); }); app.listen(3001, () => { console.log("Adapter running on port 3001"); });
Install the dependencies and start the adapter:
npm init -y
npm install express
node adapter.js
The adapter exposes two endpoints. /webhook/email receives inbound email from Dead Simple and creates Paperclip tickets. /callback/send-reply accepts reply instructions from Paperclip agents and sends email through the Dead Simple API. The HMAC signature verification on the webhook endpoint ensures that only legitimate Dead Simple payloads are processed.
Step 4: Configure Webhooks
Now we need to tell Dead Simple where to deliver inbound emails. Register a webhook for each inbox:
# Register webhook for the Sales inbox curl -X POST https://api.deadsimple.email/v1/webhooks \ -H "Authorization: Bearer dse_..." \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-adapter.example.com/webhook/email", "events": ["message.received"], "inbox_id": "inb_s4l3s7x9k2" }' # Register webhook for Support inbox curl -X POST https://api.deadsimple.email/v1/webhooks \ -H "Authorization: Bearer dse_..." \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-adapter.example.com/webhook/email", "events": ["message.received"], "inbox_id": "inb_supp0rt3d" }' # Register webhook for Ops inbox curl -X POST https://api.deadsimple.email/v1/webhooks \ -H "Authorization: Bearer dse_..." \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-adapter.example.com/webhook/email", "events": ["message.received"], "inbox_id": "inb_0ps4g3nt1" }'
Each webhook registration returns a secret field — this is the HMAC-SHA256 key used to sign every webhook payload. Store it securely and set it as your WEBHOOK_SECRET environment variable. If you use the same URL for all three webhooks, you can use the same secret by passing it in the request, or you can let Dead Simple generate unique secrets per webhook and verify based on the inbox ID.
Dead Simple signs every webhook payload by computing HMAC-SHA256(secret, request_body) and including the result in the X-DeadSimple-Signature header. Your adapter should always verify this signature before processing the payload — this prevents spoofed requests from creating fake tickets in your Paperclip org.
Step 5: Wire Up Agent Email Actions
Now let's give your Paperclip agents the ability to send email. In Paperclip, agents execute tasks through actions — reusable functions that agents can call as part of their workflows. We'll create an email action that any agent can invoke.
Here's a Python utility that wraps the Dead Simple API for sending email. This runs as a Paperclip action handler:
import os import requests import logging logger = logging.getLogger("email_action") API_BASE = "https://api.deadsimple.email/v1" API_KEY = os.environ["DEADSIMPLE_API_KEY"] HEADERS = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", } # Map Paperclip agent roles to Dead Simple inbox IDs AGENT_INBOXES = { "sales-agent": "inb_s4l3s7x9k2", "support-agent": "inb_supp0rt3d", "ops-agent": "inb_0ps4g3nt1", } def send_email(agent_role, to, subject, body, html=None): """Send an email from the inbox assigned to this agent role. Args: agent_role: The Paperclip agent role (e.g., "sales-agent") to: Recipient email address subject: Email subject line body: Plain text email body html: Optional HTML body Returns: dict with message_id and status, or None on failure """ inbox_id = AGENT_INBOXES.get(agent_role) if not inbox_id: logger.error(f"No inbox mapped for role: {agent_role}") return None payload = {"to": to, "subject": subject, "body": body} if html: payload["html"] = html resp = requests.post( f"{API_BASE}/inboxes/{inbox_id}/messages", headers=HEADERS, json=payload, ) resp.raise_for_status() data = resp.json()["data"] logger.info(f"Email sent: {data['message_id']} via {inbox_id}") return data def get_inbox_messages(agent_role, limit=20): """Fetch recent messages for an agent's inbox.""" inbox_id = AGENT_INBOXES.get(agent_role) if not inbox_id: return [] resp = requests.get( f"{API_BASE}/inboxes/{inbox_id}/messages", headers=HEADERS, params={"limit": limit}, ) resp.raise_for_status() return resp.json()["data"]
This utility integrates with Paperclip's task system. When an agent decides to send an email, it calls send_email with its role, the recipient, subject, and body. The utility looks up the correct inbox and sends via the Dead Simple API. The agent never needs to know the inbox ID — the role mapping handles that automatically.
For Paperclip's heartbeat system, you can add a periodic check that polls for new messages. This is useful for agents that need to proactively check their inbox rather than waiting for webhook-driven tickets:
# In your Paperclip agent's heartbeat handler
messages = get_inbox_messages("sales-agent", limit=5)
for msg in messages:
if msg["status"] == "unread":
# Create a ticket for this message
create_ticket(msg)
Step 6: Build a Complete Multi-Agent Email Workflow
Let's put it all together with a realistic scenario. A potential customer emails your sales address. The Sales agent qualifies the lead, the Support agent handles onboarding, and the Ops agent provisions their account. Each agent sends from their own inbox, creating a clear audit trail.
Here's the Paperclip org configuration:
org: name: "YourCo Agents" agents: - role: "sales-agent" name: "Sales Agent" model: "gpt-4o" capabilities: - "qualify-leads" - "send-email" - "escalate-to-support" system_prompt: | You are a sales agent. Qualify inbound leads by assessing company size, use case, and budget. Reply within 5 minutes. Escalate qualified leads to support for onboarding. - role: "support-agent" name: "Support Agent" model: "gpt-4o" capabilities: - "onboard-customer" - "send-email" - "escalate-to-ops" system_prompt: | You are a support agent. Onboard new customers by sending welcome emails, documentation links, and setup instructions. Escalate provisioning requests to the ops agent. - role: "ops-agent" name: "Operations Agent" model: "gpt-4o" capabilities: - "provision-account" - "send-email" system_prompt: | You are an operations agent. Provision new customer accounts, configure access, and send confirmation emails with credentials. workflows: - name: "new-lead" trigger: "inbound-email" steps: - agent: "sales-agent" action: "qualify-leads" - agent: "support-agent" action: "onboard-customer" condition: "lead.qualified == true" - agent: "ops-agent" action: "provision-account" condition: "onboarding.complete == true"
Here's how the email trail looks from the customer's perspective:
- Customer sends: "Hi, I'm interested in your API for our AI platform. We have about 50 agents that need email." →
sales@yourco.deadsimple.email - Sales Agent replies (from
sales@yourco.deadsimple.email): "Thanks for reaching out! With 50 agents, our Pro plan at $29/mo would be a great fit. I'm looping in our support team to get you set up." - Support Agent emails (from
support@yourco.deadsimple.email): "Welcome! Here's your getting started guide and API documentation. I've asked our ops team to provision your account." - Ops Agent emails (from
ops@yourco.deadsimple.email): "Your account is ready. Here are your API credentials and your subdomain configuration details."
Each email comes from a different address, maintaining clear role separation. The customer can reply to any of them and the message routes to the correct agent. Paperclip's ticket system tracks every interaction, and Dead Simple's threading ensures conversation continuity.
Step 7: Monitor and Govern
Running autonomous email agents without monitoring is a recipe for trouble. Paperclip provides governance features — approval chains, rate limits, audit logs — and Dead Simple provides email-specific usage metrics. Combine both for full visibility.
Here's a monitoring script that pulls usage data from the Dead Simple API and logs it alongside Paperclip's audit trail:
import os import requests API_BASE = "https://api.deadsimple.email/v1" HEADERS = { "Authorization": f"Bearer {os.environ['DEADSIMPLE_API_KEY']}", } def check_usage(): """Pull email usage metrics from Dead Simple.""" resp = requests.get(f"{API_BASE}/usage", headers=HEADERS) usage = resp.json()["data"] print(f"Emails sent: {usage['emails_sent']}") print(f"Emails received: {usage['emails_received']}") print(f"Active inboxes: {usage['active_inboxes']}") print(f"Webhook deliveries: {usage['webhook_deliveries']}") # Alert if approaching plan limits if usage["emails_sent"] > usage["plan_limit"] * 0.8: print("WARNING: Approaching monthly email limit!") return usage def audit_agent_emails(inbox_id, agent_name): """List recent emails sent by a specific agent.""" resp = requests.get( f"{API_BASE}/inboxes/{inbox_id}/messages", headers=HEADERS, params={"direction": "sent", "limit": 10}, ) messages = resp.json()["data"] print(f"\n--- {agent_name} sent emails (last 10) ---") for msg in messages: print(f" {msg['created_at']} | To: {msg['to']} | {msg['subject']}") if __name__ == "__main__": check_usage() audit_agent_emails("inb_s4l3s7x9k2", "Sales Agent") audit_agent_emails("inb_supp0rt3d", "Support Agent") audit_agent_emails("inb_0ps4g3nt1", "Ops Agent")
Run this on a cron schedule or as part of your Paperclip org's governance loop. The combination of Paperclip's approval chains (requiring human review for certain email types) and Dead Simple's per-inbox metrics gives you full control over what your agents are sending.
Dead Simple API Quick Reference for Paperclip Users
Here are the endpoints you'll use most when integrating with Paperclip:
| Endpoint | Method | Description |
|---|---|---|
/v1/inboxes |
POST | Create a new inbox for an agent |
/v1/inboxes/bulk |
POST | Create multiple inboxes at once |
/v1/inboxes |
GET | List all inboxes in your account |
/v1/inboxes/{id}/messages |
POST | Send an email from an agent's inbox |
/v1/inboxes/{id}/messages |
GET | List messages in an inbox |
/v1/inboxes/{id}/threads |
GET | List conversation threads |
/v1/webhooks |
POST | Register a webhook for inbound email |
/v1/usage |
GET | Get email volume and usage metrics |
All endpoints accept JSON and require an Authorization: Bearer dse_... header. Rate limits are generous: 100 requests/second on the Pro plan, 500/second on Scale. Full API documentation is available at deadsimple.email/docs.
Cost Comparison: Dead Simple vs Alternatives for Agent Teams
Agent teams need lots of inboxes. Traditional email providers charge per-seat, which gets expensive fast. Here's how Dead Simple compares:
| Team Size | Dead Simple | Google Workspace | Monthly Savings |
|---|---|---|---|
| 10 agent inboxes | $5/mo (Hobby) | $84/mo | $79/mo (94%) |
| 50 agent inboxes | $29/mo (Pro) | $420/mo | $391/mo (93%) |
| 200 agent inboxes | $99/mo (Scale) | $1,680/mo | $1,581/mo (94%) |
Beyond raw cost, Dead Simple eliminates several headaches that plague agent teams using traditional email providers:
- No OAuth complexity. Google and Microsoft require OAuth2 token management, refresh flows, and consent screens. Dead Simple uses a single API key.
- No account suspensions. Traditional providers flag automated sending patterns as suspicious. Dead Simple is built for programmatic use.
- HMAC webhooks included. Real-time inbound email with cryptographic verification, no polling required.
- Instant provisioning. Create 100 inboxes in a single API call. No manual account setup, no waiting for admin approval.
What's Next
You now have a fully functioning Paperclip organization where AI agents send and receive email autonomously. Each agent has its own identity, incoming mail routes to the right agent automatically, and the entire email trail is auditable.
From here, you can extend the integration in several directions:
- Full API documentation — explore attachments, threading, custom domains, and batch operations
- Multi-tenant pods — if you're building a SaaS on top of Paperclip, Dead Simple's pod architecture lets you isolate email data per customer
- Custom domains — move from
@yourco.deadsimple.emailto@yourcompany.comwith automated DNS configuration - Clipmart integration — we're working on a native Dead Simple Email adapter for Paperclip's Clipmart marketplace, so you'll be able to install email capabilities with a single click
Check out our other guides for more on building with Dead Simple:
- Getting Started with Dead Simple Email — the 5-minute quickstart
- Webhooks for AI Email — deep dive into webhook configuration and best practices
- Why AI Agents Need Email — the case for giving every agent an inbox
Questions or feedback? Email us at hello@deadsimple.email or join the Discord community.