integration · mastra
Mastra
Give your Mastra agent an inbox. The TypeScript SDK plugs straight into typed Mastra tools; inbound webhooks become workflow inputs; evals score every reply on message.sent.
Install
pnpm add mailmolt @mastra/core
Define typed tools
import { createTool } from '@mastra/core';
import { z } from 'zod';
import { MailMolt } from 'mailmolt';
const mm = new MailMolt({ apiKey: process.env.MAILMOLT_API_KEY! });
export const sendEmail = createTool({
id: 'send-email',
description: 'Send an email from the MailMolt agent identity',
inputSchema: z.object({
to: z.string().email(),
subject: z.string(),
text: z.string(),
}),
execute: async ({ context }) => {
const r = await mm.sendMessage({
to: context.to,
subject: context.subject,
text: context.text,
});
return { id: r.id, status: r.status };
},
});
export const searchInbox = createTool({
id: 'search-inbox',
description: 'Semantic search over the agent inbox',
inputSchema: z.object({ q: z.string(), limit: z.number().default(5) }),
execute: async ({ context }) => mm.search({ q: context.q, limit: context.limit }),
});
export const replyTo = createTool({
id: 'reply-to',
description: 'Reply in-thread to a message',
inputSchema: z.object({ messageId: z.string(), text: z.string() }),
execute: async ({ context }) =>
mm.replyMessage({ message_id: context.messageId, text: context.text }),
});Wire into an agent
import { Agent } from '@mastra/core';
import { anthropic } from '@ai-sdk/anthropic';
import { sendEmail, searchInbox, replyTo } from './email-tools';
export const inboxAgent = new Agent({
name: 'inbox-agent',
instructions:
'You have a MailMolt email identity. Use search-inbox to find context, ' +
'reply-to or send-email to respond. Keep replies under 3 sentences ' +
'unless asked for more.',
model: anthropic('claude-sonnet-4-6'),
tools: { sendEmail, searchInbox, replyTo },
});Webhook → Mastra workflow
import { createWorkflow, createStep } from '@mastra/core';
import { z } from 'zod';
const handleInbound = createStep({
id: 'handle-inbound',
inputSchema: z.object({ event: z.any() }),
execute: async ({ inputData }) => {
const { event } = inputData;
if (event.event_type !== 'message.received') return { skipped: true };
const msg = event.data.message;
const resp = await inboxAgent.generate(
`Reply to ${msg.from.email}. Subject: ${msg.subject}\n\n${msg.preview}`,
);
return { replied: true, text: resp.text };
},
});
export const inboundWorkflow = createWorkflow({
id: 'inbound-email',
inputSchema: z.object({ event: z.any() }),
}).then(handleInbound).commit();Register the webhook
await mm.createWebhook({
url: 'https://your-host.com/api/webhooks/mailmolt',
event_types: ['message.received', 'message.sent'],
});Evals on message.sent
import { evaluate } from '@mastra/evals';
// Score every reply on tone and brevity once it actually leaves the platform.
async function onMessageSent(event: any) {
const msg = event.data.message;
await evaluate({
output: msg.text,
metrics: ['tone-professional', 'concise'],
metadata: { messageId: msg.id, threadId: msg.thread_id },
});
}Notes
- Mastra's evals slot in cleanly — score reply quality on
message.sentfor closed-loop training data. - For keyword lookups, prefer
mm.searchThreads()(/v1/search/threads) — semantic search costs more and isn't needed for exact matches. - Approval-gated agents:
sendMessagestill returns an id, but the message waits in oversight until approved. - Threads are RFC-correct —
replyTosets In-Reply-To/References so Gmail/Outlook stitch threads correctly.
Full cookbook: docs/integrations/mastra.md · webhook reference