If you’re running an AI agent like OpenClaw, you’ve probably hit this problem: how do you give your agent access to email?
The obvious approaches all have issues:
- Gmail API — OAuth complexity, Google’s rate limits, privacy concerns
- IMAP polling — Wasteful, slow, rate-limited by providers
- Third-party services — Monthly costs, vendor lock-in, only handle sending
We wanted something better: a self-hosted solution where incoming emails trigger webhooks (push, not poll!) and sending works via a simple HTTP API. After researching the options, we built exactly that.
The Architecture
Here’s what we ended up with:
┌─────────────────────────────────────────────────────────────┐
│ HETZNER SERVER │
│ │
│ ┌─────────────┐ ┌─────────────────────────────┐ │
│ │ │ webhook │ │ │
│ │ mox │────────▶│ moltbot-email-webhook │ │
│ │ mail server │ │ (Node.js) │ │
│ │ │◀────────│ │ │
│ └─────────────┘ API └──────────────┬──────────────┘ │
│ │ │ │
│ │ SMTP/IMAP │ HTTP │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Webmail │ │ OpenClaw │ │
│ │ Clients │ │ (MoltBot) │ │
│ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Key components:
- mox mail server — Modern, single-binary mail server with built-in webhook support
- Webhook endpoint — Node.js service that receives email notifications
- HTTP API — For sending emails without SMTP libraries
- OpenClaw integration — Agent can check/send email via simple CLI
Why mox?
We evaluated several options:
| Solution | Pros | Cons |
|---|---|---|
| docker-mailserver | Mature, proven | Complex, no webhooks |
| mailcow | Great UI | Resource heavy |
| iRedMail | Full featured | Enterprise focus |
| mox | Single binary, webhooks, HTTP API | Newer project |
mox won because it has exactly what AI agents need:
- Webhooks for incoming mail — No polling required
- HTTP/JSON API for sending — No SMTP library needed
- Single binary — Easy to deploy and maintain
- Auto TLS — Let’s Encrypt built-in
- Modern defaults — SPF, DKIM, DMARC configured automatically
Setting Up mox
Prerequisites
- A VPS with a dedicated IP (we use Hetzner Cloud, €4.51/month)
- A domain you control
- Basic Linux knowledge
Installation
SSH into your server and download mox:
# Create mox user
useradd -m -s /bin/bash mox
cd /home/mox
# Download latest release
curl -LO https://github.com/mjl-/mox/releases/latest/download/mox-linux-amd64
chmod +x mox-linux-amd64
mv mox-linux-amd64 mox
# Run quickstart (creates config, generates passwords)
./mox quickstart [email protected]
The quickstart outputs DNS records you need to add:
Type Name Value
MX @ mail.yourdomain.com (priority 10)
A mail your-server-ip
TXT @ v=spf1 ip4:your-ip mx ~all
TXT _dmarc v=DMARC1;p=reject;...
TXT selector._domainkey v=DKIM1;k=rsa;p=...
Add these to your DNS provider, then verify:
./mox dnscheck yourdomain.com
Running mox as a Service
Create a systemd service:
cat > /etc/systemd/system/mox.service << 'EOF'
[Unit]
Description=mox mail server
After=network.target
[Service]
User=mox
WorkingDirectory=/home/mox
ExecStart=/home/mox/mox serve
Restart=always
[Install]
WantedBy=multi-user.target
EOF
systemctl enable mox
systemctl start mox
The Webhook Integration
mox can POST incoming emails to a URL. We built a small Node.js service to receive these:
// moltbot-email-webhook.js
import express from 'express';
import fs from 'fs/promises';
const app = express();
app.use(express.json({ limit: '50mb' }));
const STORAGE_DIR = './emails';
// Receive incoming emails from mox
app.post('/webhook/incoming', async (req, res) => {
const { from, to, subject, body, received } = req.body;
const email = {
id: Date.now().toString(),
from,
to,
subject,
body,
received: received || new Date().toISOString(),
};
// Store email
await fs.mkdir(STORAGE_DIR, { recursive: true });
await fs.writeFile(
`${STORAGE_DIR}/${email.id}.json`,
JSON.stringify(email, null, 2)
);
console.log(`📧 Received: ${subject} from ${from}`);
res.json({ status: 'ok', id: email.id });
});
// List recent emails
app.get('/emails', async (req, res) => {
const files = await fs.readdir(STORAGE_DIR).catch(() => []);
const emails = await Promise.all(
files
.filter(f => f.endsWith('.json'))
.slice(-50)
.map(async f => {
const content = await fs.readFile(`${STORAGE_DIR}/${f}`, 'utf-8');
return JSON.parse(content);
})
);
res.json(emails.reverse());
});
// Send email via mox API
app.post('/send', async (req, res) => {
const { to, subject, body } = req.body;
const response = await fetch('http://localhost:1080/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
from: '[email protected]',
to: [to],
subject,
text: body,
}),
});
const result = await response.json();
res.json(result);
});
app.listen(3847, () => {
console.log('🦞 Email webhook running on port 3847');
});
Configure mox to send webhooks:
mox config set webhooks.incoming.url http://localhost:3847/webhook/incoming
OpenClaw Integration
Now the fun part: letting your AI agent use email. We created a simple CLI:
// email-client.js
const API = 'http://localhost:3847';
async function check() {
const res = await fetch(`${API}/emails`);
const emails = await res.json();
if (emails.length === 0) {
console.log('No emails.');
return;
}
console.log(`📬 ${emails.length} recent emails:\n`);
for (const email of emails.slice(0, 5)) {
console.log(`From: ${email.from}`);
console.log(`Subject: ${email.subject}`);
console.log(`Date: ${email.received}`);
console.log('---');
}
}
async function send(to, subject, body) {
const res = await fetch(`${API}/send`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ to, subject, body }),
});
const result = await res.json();
console.log(result.ok ? '✅ Sent!' : '❌ Failed');
}
// CLI handling
const [,, cmd, ...args] = process.argv;
if (cmd === 'check') check();
else if (cmd === 'send') send(args[0], args[1], args[2]);
else console.log('Usage: node email-client.js [check|send <to> <subject> <body>]');
In your OpenClaw config, add to HEARTBEAT.md:
## Email
Run: `cd scripts && node email-client.js check`
- Webhook-based, no rate limiting needed
- Check anytime, instant results
And TOOLS.md:
## Email
- **Address:** [email protected]
- **Check:** `node scripts/email-client.js check`
- **Send:** `node scripts/email-client.js send <to> <subject> <body>`
Why This Matters for AI Agents
Traditional email integration requires:
- Complex OAuth flows (Gmail, Outlook)
- IMAP polling with rate limits
- Heavy email parsing libraries
- Dealing with attachments and HTML
Our solution:
- Push-based — Emails arrive instantly via webhook
- Simple API — JSON in, JSON out
- No dependencies — Just HTTP calls
- Private — Self-hosted, your data stays yours
The agent doesn’t need to understand IMAP or OAuth. It just checks an HTTP endpoint and gets clean JSON.
Results
After running this setup for a few weeks:
- Reliability: 100% uptime, zero missed emails
- Speed: Webhooks deliver in under 100ms
- Cost: Just the VPS cost (~€5/month)
- Maintenance: Basically none
The biggest win is the simplicity. When OpenClaw checks email, it’s a single HTTP call that returns immediately. No polling intervals, no “checking in 5 minutes,” no rate limit errors.
Lessons Learned
- Webhooks beat polling — Always prefer push when possible
- mox is production-ready — Despite being newer, it’s solid
- HTTP APIs simplify agent integration — Avoid protocol complexity
- Self-hosting email is easier than expected — With the right tools
What’s Next
We’re considering adding:
- Magic link authentication for web services
- Email-to-task conversion (send email, create todo)
- Attachment handling for document processing
If you’re building AI agents that need email access, give mox a try. It’s the cleanest solution we’ve found.
Have questions? Email us at [email protected] (yes, using this exact setup).