mailmolt
integration · hermes

Hermes

MailMolt's TypeScript SDK plugs straight into a Hermes agent as a tool registry. Inbound mail routes through a webhook receiver into agent.run(); outbound goes through governed quotas, the approval queue, and trust scoring.

Install

pnpm add @mailmolt/sdk @hermesai/runtime
# Capture an API key for this agent:
curl -X POST https://api.mailmolt.com/v1/agents/register \
  -H 'Content-Type: application/json' \
  -d '{"name": "hermes-agent", "owner_hint": "ops bot"}'

Expose MailMolt as Hermes tools

import { MailMolt } from '@mailmolt/sdk';
import { defineTool } from '@hermesai/runtime';

const mm = new MailMolt({ apiKey: process.env.MAILMOLT_API_KEY! });

export const sendEmail = defineTool({
  name: 'send_email',
  description: 'Send an email from this Hermes agent.',
  schema: {
    to: 'string',
    subject: 'string',
    text: 'string',
  },
  async run({ to, subject, text }) {
    const r = await mm.sendMessage({ to, subject, text });
    return { id: r.id, status: 'sent' };
  },
});

export const searchInbox = defineTool({
  name: 'search_inbox',
  description: 'Semantic search across the inbox.',
  schema: { query: 'string' },
  async run({ query }) {
    const r = await mm.search({ q: query, limit: 5 });
    return { hits: r.data };
  },
});

export const replyTo = defineTool({
  name: 'reply_to',
  description: 'Reply in-thread to a message.',
  schema: { message_id: 'string', text: 'string' },
  async run({ message_id, text }) {
    const r = await mm.replyMessage({ messageId: message_id, text });
    return { id: r.id };
  },
});

Wire tools into a Hermes agent

import { Agent } from '@hermesai/runtime';
import { sendEmail, searchInbox, replyTo } from './mailmolt-tools';

export const support = new Agent({
  name: 'support',
  model: 'claude-sonnet-4-6',
  systemPrompt: `You handle support email. Search before replying.
Keep replies under 4 sentences. Never promise refunds without a ticket.`,
  tools: [sendEmail, searchInbox, replyTo],
});

await support.run(
  'A new email arrived asking about API rate limits. Reply with the docs link.'
);

Receive mail as agent input

import { Hono } from 'hono';
import { support } from './agent';

// MailMolt posts message.received whenever new mail arrives. Wire it
// straight into the Hermes agent runtime.
const app = new Hono();

app.post('/webhooks/mailmolt', async (c) => {
  const evt = await c.req.json();
  if (evt.event_type === 'message.received') {
    const m = evt.data.message;
    await support.run(
      `New email from ${m.from.email}: ${m.subject}\n\n${m.preview}\n\nReply if relevant; otherwise note "skipped" and move on.`
    );
  }
  return c.json({ ok: true });
});

// Register once:
await mm.createWebhook({
  url: 'https://your-host.com/webhooks/mailmolt',
  eventTypes: ['message.received'],
});

export default app;

Human-in-the-loop on sensitive sends

// Approval-gated sends — pause humans-in-the-loop in oversight.
await mm.sendMessage({
  to: 'finance@bigco.com',
  subject: 'Refund request — ticket #4892',
  text: 'Refunding $4,200 per the customer note.',
  require_approval: true,    // queues until a human approves
});

// In oversight: /flagged shows the pending send with a 7-day expiry,
// audit trail, and an approve / reject button. Approval-gated sends
// still return an id; the queue resolves out-of-band.

Notes

  • The SDK is fully typed — Hermes' tool schemas inherit MailMolt's parameter types so the model gets accurate signatures, not stringly-typed JSON.
  • Hermes loops can fan out — mint one MailMolt agent per Hermes role (support, billing, dev-ops) so trust scores stay attributable. Per-agent caps surface in the run-time as RateLimitError.
  • Long-running Hermes sessions stay coherent across replies because each replyMessage uses RFC2822 In-Reply-To/References.
  • Persist the webhook secret in Hermes's env, then verify X-MailMolt-Signature on every inbound — the SDK exposes verifyWebhook(req, secret).

Runnable example: sdks/typescript/examples/hermes-runtime.ts · webhook reference