DiviMail MCP Server
DiviMail ships a Model Context Protocol server (v1.2.0) with 10 tools covering the full email lifecycle — register, authenticate, send, read, reply, and top up. Install once; use it from any MCP-compatible client.
Install
Smithery (recommended — any MCP client)
npx @smithery/cli install patch/divimail-mcp
View listing: https://smithery.ai/server/patch/divimail-mcp
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"divimail": {
"command": "node",
"args": ["/path/to/divimail-mcp/index.js"],
"env": {
"DIVIMAIL_SERVICE_SECRET": "your-secret-here"
}
}
}
}
Goose / VonCluer
Add to ~/.config/goose/config.yaml:
extensions:
divimail:
enabled: true
type: stdio
name: divimail
cmd: node
args:
- /path/to/divimail-mcp/index.js
envs:
DIVIMAIL_SERVICE_SECRET: your-secret-here
timeout: 30
Generic (any stdio MCP client)
command: node
args: ["/path/to/divimail-mcp/index.js"]
env: { DIVIMAIL_SERVICE_SECRET: "your-secret-here" }
Tools (10)
| Tool | Auth | Description |
|---|---|---|
divimail_register |
No | Register a new @divindi.io address |
divimail_challenge |
No | Get a challenge string — step 1 of wallet auth |
divimail_authenticate |
No | Submit Ed25519 signature, receive JWT |
divimail_service_token |
No† | Get JWT via service secret (agents / automation) |
divimail_send |
JWT | Send email to any address — $0.01 USDC |
divimail_inbox |
JWT | List inbox messages with read/unread status |
divimail_read |
JWT | Read full message content |
divimail_mark_read |
JWT | Mark a message as read |
divimail_balance |
No | Check USDC balance for any @divindi.io address |
divimail_request_topup |
JWT | Get deposit address + cross-chain NEAR Intents links |
†divimail_service_token requires DIVIMAIL_SERVICE_SECRET in the process environment.
Authentication — two paths
Path A: Service token (for automated agents)
No NEAR signing required. Use this for scheduled tasks, background agents, and any automated workflow.
divimail_service_token({
accountId: "myagent.divimail.near",
serviceSecret: process.env.DIVIMAIL_SERVICE_SECRET
})
→ { token: "eyJ..." } // valid 7 days
The DIVIMAIL_SERVICE_SECRET is a shared secret issued by the DiviMail operator.
Set it in the MCP server's environment (see install examples above). The token
returned has the same shape and permissions as the wallet-auth token.
Path B: NEAR wallet signature (for interactive users)
// Step 1 — request a one-time challenge
divimail_challenge({ accountId: "myname.near" })
→ { message: "Sign this message to authenticate with DiviMail: <nonce>", challenge: "..." }
// Step 2 — sign the message with your NEAR wallet (ed25519)
// Step 3 — submit
divimail_authenticate({ accountId, signature, publicKey })
→ { token: "eyJ..." }
Common patterns
1. Register + send your first email
// Register (no wallet needed — DiviMail provisions one)
divimail_register({ emailAlias: "myagent", provision: true })
→ { email: "[email protected]", nearAccountId: "myagent.divimail.near" }
// Get a service token
divimail_service_token({ accountId: "myagent.divimail.near", serviceSecret: "..." })
→ { token: "eyJ..." }
// Send
divimail_send({
from: "[email protected]",
to: "[email protected]",
subject: "Nightly digest",
text: "3 alerts were triaged...",
token
})
→ { messageId: "...", balanceAfter: 2.89, costUsdc: 0.01 }
2. Check inbox and reply to trusted senders
This is the recommended pattern for agents that monitor their inbox on a schedule (e.g. hourly) and respond automatically.
// Get token
divimail_service_token({ accountId: "myagent.divimail.near", serviceSecret: "..." })
// Check inbox
divimail_inbox({ email: "[email protected]", token, limit: 20 })
→ [{ messageId, from, subject, isRead: false, receivedAt }, ...]
// For each unread message — read it
divimail_read({ email: "[email protected]", messageId, token })
→ { from, subject, bodyText, bodyHtml, receivedAt }
// Mark it read immediately
divimail_mark_read({ email: "[email protected]", messageId, token })
// Decide: reply, forward, or silently discard
// (see Trust model below)
3. Trust model — reply vs. forward
Agents should not reply to arbitrary senders — that opens them to spam and social engineering. A simple allow-list approach:
- Trusted sender (allow-listed): read the message, decide if a response is warranted (question / request detected), reply in the agent's voice.
- Untrusted sender: forward the full message to a designated human address; do not reply to the original sender.
Example forward:
divimail_send({
from: "[email protected]",
to: "[email protected]",
subject: "[Fwd] " + originalSubject + " — from " + senderEmail,
text: "Hi Jimmy,\n\nI received mail from an untrusted sender and am forwarding it.\n\nFrom: " + senderEmail + "\n\n---\n\n" + bodyText,
token
})
The agent does not reply to the original untrusted sender.
4. Low balance — top up
divimail_balance({ email: "[email protected]" })
→ { balanceUsdc: 0.18 } // getting low
divimail_request_topup({ alias: "myagent", token })
→ {
depositAddress: "myagent.divimail.near",
chainInstructions: {
near: { deepLink, note: "direct, ~30s" },
base: { deepLink, note: "bridge, ~2-5 min" },
ethereum: { deepLink, note: "bridge, ~5-10 min" },
arbitrum: { deepLink, note: "bridge, ~2-5 min" },
solana: { deepLink, note: "bridge, ~2-5 min" }
}
}
Send USDC to depositAddress on NEAR mainnet, or use any deepLink to bridge
from another chain via NEAR Intents. Balance is credited within 60 seconds.
Setting up automated inbox monitoring (without Goose recipes)
If your agent runtime does not have a built-in scheduler, you can use any of these:
Cron job (Linux / macOS):
# Run every hour
0 * * * * node /path/to/your-inbox-check-script.js >> /var/log/divimail-inbox.log 2>&1
Systemd timer (Linux):
# /etc/systemd/system/divimail-inbox.timer
[Timer]
OnCalendar=hourly
Persistent=true
[Install]
WantedBy=timers.target
GitHub Actions (cloud-hosted agents):
on:
schedule:
- cron: '0 * * * *'
jobs:
inbox:
runs-on: ubuntu-latest
steps:
- run: node inbox-check.js
env:
DIVIMAIL_SERVICE_SECRET: ${{ secrets.DIVIMAIL_SERVICE_SECRET }}
Node.js (in-process):
setInterval(async () => {
const { token } = await getServiceToken();
const { emails } = await getInbox("[email protected]", token);
for (const msg of emails.filter(m => !m.isRead)) {
await processMessage(msg, token);
}
}, 60 * 60 * 1000); // every hour
Goose / VonCluer recipe:
See the project wiki for the full divimail_inbox_check.yaml recipe — a
ready-to-use hourly scheduled recipe for Goose that checks all 4 agent inboxes,
applies the trust model, and logs results to the Activity Feed.
Inbox check script (standalone Node.js)
Minimal working example — no MCP client required, pure REST:
// inbox-check.js
const https = require('https');
const API = 'divindi.io';
const AGENT_EMAIL = '[email protected]';
const AGENT_NEAR_ID = 'myagent.divimail.near';
const TRUSTED_SENDERS = ['[email protected]'];
const FORWARD_TO = '[email protected]';
const SERVICE_SECRET = process.env.DIVIMAIL_SERVICE_SECRET;
function api(method, path, body, token) {
return new Promise((resolve, reject) => {
const payload = body ? JSON.stringify(body) : null;
const headers = { 'Content-Type': 'application/json' };
if (token) headers['Authorization'] = `Bearer ${token}`;
if (payload) headers['Content-Length'] = Buffer.byteLength(payload);
const req = https.request({ hostname: API, port: 443, path: `/api${path}`, method, headers }, res => {
let data = '';
res.on('data', c => data += c);
res.on('end', () => resolve(JSON.parse(data)));
});
req.on('error', reject);
if (payload) req.write(payload);
req.end();
});
}
async function run() {
// 1. Get token
const { data: { token } } = await api('POST', '/auth/service-token', {
accountId: AGENT_NEAR_ID,
serviceSecret: SERVICE_SECRET
});
// 2. Get unread messages
const { data: { emails } } = await api('GET', `/emails/${encodeURIComponent(AGENT_EMAIL)}/inbox?limit=20`, null, token);
const unread = emails.filter(m => !m.isRead);
if (!unread.length) { console.log('No new mail.'); return; }
for (const msg of unread) {
// 3. Read full content
const { data: full } = await api('GET', `/emails/${encodeURIComponent(AGENT_EMAIL)}/${msg.messageId}`, null, token);
// 4. Mark read
await api('PUT', `/emails/${encodeURIComponent(AGENT_EMAIL)}/${msg.messageId}/read`, {}, token);
const trusted = TRUSTED_SENDERS.includes(full.from);
const wantsReply = /\?|please|can you|could you|help|what|when|how|let me know|send me|check|update/i.test(full.bodyText || '');
if (trusted && wantsReply) {
// 5a. Reply
await api('POST', '/send', {
from: AGENT_EMAIL,
to: full.from,
subject: `Re: ${full.subject}`,
text: `Hi Jimmy,\n\nThanks for your message. [Agent reply here]\n\n— MyAgent`
}, token);
console.log(`Replied to: ${full.subject}`);
} else if (!trusted) {
// 5b. Forward
await api('POST', '/send', {
from: AGENT_EMAIL,
to: FORWARD_TO,
subject: `[Fwd] ${full.subject} — from ${full.from}`,
text: `Hi Jimmy,\n\nForwarding from untrusted sender: ${full.from}\n\n---\n\n${full.bodyText}`
}, token);
console.log(`Forwarded: ${full.subject}`);
} else {
console.log(`Read (no reply needed): ${full.subject}`);
}
}
}
run().catch(console.error);
Transport
Stdio. The MCP server relays all calls to the DiviMail REST API at https://divindi.io/api.
No local port is required. Works in any process that can spawn a child process.