Install
openclaw skills install @di5cip1e/director-email-setupSet up a dedicated email address for an agent using Resend. Configure sending, receiving via webhook, inbox storage, and automated monitoring. Use when: (1) agent needs its own email identity, (2) agent needs to receive confirmation emails from external services, (3) setting up agent email for the first time, (4) agent needs to autonomously handle email verification flows.
openclaw skills install @di5cip1e/director-email-setupGive an agent its own email address for sending and receiving — verification codes, notifications, account registration, and autonomous inbox monitoring.
curl, jq, and node available on the serverExternal Sender
↓
[Resend Inbound]
↓ (webhook: email.received)
[Express /api/inbound-email]
↓ (save as JSON)
[mail/inbox/]
↓ (cron: every 5 min)
[Agent reads + processes]
↓
[Agent responds/acts]
If the domain isn't already verified:
RESEND_KEY="re_xxxxx"
# Add domain
curl -s -X POST -H "Authorization: Bearer $RESEND_KEY" \
-H "Content-Type: application/json" \
https://api.resend.com/domains \
-d '{"name": "yourdomain.com", "region": "us-east-1"}'
# Get DNS records to configure
curl -s -H "Authorization: Bearer $RESEND_KEY" \
https://api.resend.com/domains/yourdomain.com | jq '.records'
Add the returned DNS records (TXT, MX, CNAME) to your DNS provider. Wait for verification (usually minutes).
Verify status:
curl -s -H "Authorization: Bearer $RESEND_KEY" \
https://api.resend.com/domains/yourdomain.com | jq '{status, capabilities}'
Both sending and receiving must be "enabled".
Create routes/inboundEmail.js in your Express backend (see references/inboundEmail.js for full implementation). Key requirements:
/api/inbound-emailRESEND_WEBHOOK_SECRETmail/inbox/Mount in your server:
const inboundEmailRoutes = require('./routes/inboundEmail');
app.use('/api/inbound-email', inboundEmailRoutes);
curl -s -X POST -H "Authorization: Bearer $RESEND_KEY" \
-H "Content-Type: application/json" \
https://api.resend.com/webhooks \
-d '{
"endpoint": "https://yourdomain.com/api/inbound-email",
"events": ["email.received"],
"enabled": true
}' | jq '{id, signing_secret}'
Save the signing_secret — you'll need it for webhook verification.
Add to your secrets management:
{
"RESEND_API_KEY": "re_xxxxx",
"RESEND_WEBHOOK_SECRET": "whsec_xxxxx",
"AGENT_EMAIL": "agent@yourdomain.com"
}
mkdir -p mail/inbox
touch mail/inbox/.lastcheck
Create a cron job that checks for new emails every 5 minutes and notifies the agent:
Every 5 min → Check mail/inbox/ for new .json files
→ If new emails: read, summarize, and report to owner
→ Mark processed with .processed extension
Send a test email:
curl -s -X POST -H "Authorization: Bearer $RESEND_KEY" \
-H "Content-Type: application/json" \
https://api.resend.com/emails \
-d '{
"from": "Agent <agent@yourdomain.com>",
"to": ["owner@theirdomain.com"],
"subject": "Email system test",
"text": "This is a test from my new email address."
}'
Verify receiving:
curl -s https://yourdomain.com/api/inbound-email | jq .
Verify inbox:
ls -la mail/inbox/
async function sendEmail(to, subject, text, html) {
const res = await fetch('https://api.resend.com/emails', {
method: 'POST',
headers: {
'Authorization': `Bearer ${RESEND_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
from: 'Agent <agent@yourdomain.com>',
to: [to],
subject,
text,
...(html && { html }),
}),
});
return res.json();
}
async function getInbox() {
const res = await fetch('http://localhost:PORT/api/inbound-email');
const data = await res.json();
return data.emails;
}
async function getEmail(id) {
const res = await fetch(`http://localhost:PORT/api/inbound-email/${id}`);
return res.json();
}
async function ackEmail(id) {
await fetch(`http://localhost:PORT/api/inbound-email/${id}/ack`, { method: 'POST' });
}
Resend signs webhooks with HMAC-SHA256. Verify to prevent spoofing:
const crypto = require('crypto');
function verifySignature(rawBody, signature, secret) {
try {
const [timestamp, hash] = signature.split(',').map(s => s.trim());
if (!timestamp || !hash) return false;
const signedPayload = `${timestamp}.${rawBody}`;
const expected = crypto.createHmac('sha256', secret)
.update(signedPayload).digest('hex');
return crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(expected));
} catch {
return false;
}
}
RESEND_API_KEY or RESEND_WEBHOOK_SECRET in chat or logs| Problem | Solution |
|---|---|
| Domain not verifying | Check DNS records propagated (dig TXT yourdomain.com) |
| Webhook not firing | Verify webhook is enabled and endpoint is HTTPS |
| Signature mismatch | Ensure you're using raw body, not parsed JSON |
| Emails not arriving | Check domain receiving capability is enabled |
| Backend not receiving | Check Express route is mounted before body parsers |