This page collects the smallest useful integration examples for external agents, SDKs, and hosted orchestration systems.
Use it together with:
Read the high-level runtime metadata:
curl -sS https://mailagents-dev.izhenghaocn.workers.dev/v2/meta/runtime | jqRead the narrower compatibility contract:
curl -sS https://mailagents-dev.izhenghaocn.workers.dev/v2/meta/compatibility | jqRead the JSON Schema for contract validation:
curl -sS https://mailagents-dev.izhenghaocn.workers.dev/v2/meta/compatibility/schema | jqRead the pinned repository snapshot:
cat docs/agent-capabilities.json | jqRecommended usage:
- use
/v2/meta/runtimefor richer environment-aware discovery - use
/v2/meta/compatibilityfor long-lived branching logic - when admin routes are enabled, read
admin.mcpfrom/v2/meta/compatibilityfor stable operator-workflow discovery - use
/v2/meta/compatibility/schemain CI or SDK validation - use
docs/agent-capabilities.jsonas a fixed admin-enabled example fixture when live calls are not desirable
For signup API onboarding, call POST /public/signup first and read the
mailbox-scoped bearer token from the inline accessToken field in the
response. The configured operator delivery channel remains a backup path.
If that token expires later, call POST /public/token/reissue. The runtime
will only email the refreshed token to the original operatorEmail from signup;
it will not return the new token inline to the caller.
If the current signup API token is still valid and the agent wants to rotate it
without emailing the operator, call POST /v1/auth/token/rotate. That
authenticated route can return the rotated token inline, deliver it back to the
mailbox itself, or do both.
That same default single-mailbox self-serve token can also call the billing
self-service endpoints for its own tenant, including POST /v1/billing/topup,
POST /v1/billing/upgrade-intent, POST /v1/billing/payment/confirm, and the
matching billing read routes.
Use POST /v1/auth/tokens when you need a broader operator token, an
operator-provisioned workflow, or an environment where the mailbox was created
outside the signup API path.
Admin minting requires x-admin-secret and admin routes enabled in the environment.
curl -sS -X POST https://mailagents-dev.izhenghaocn.workers.dev/v1/auth/tokens \
-H 'content-type: application/json' \
-H 'x-admin-secret: REPLACE_WITH_ADMIN_SECRET' \
-d '{
"sub": "external-agent",
"tenantId": "t_demo",
"scopes": ["mail:read", "task:read", "draft:create", "draft:read", "draft:send"],
"mailboxIds": ["mbx_demo"],
"expiresInSeconds": 3600
}'Prefer:
- mailbox-scoped tokens
- short expirations
- only the scopes needed for the planned workflow
With a mailbox-scoped token, newer self routes remove the need to keep passing
tenantId, mailboxId, and agentId for common read and send operations.
Example:
curl -sS https://api.mailagents.net/v1/mailboxes/self \
-H "authorization: Bearer $TOKEN" | jq
curl -sS 'https://api.mailagents.net/v1/mailboxes/self/messages?limit=10' \
-H "authorization: Bearer $TOKEN" | jqFor accepted outbound sends, poll the authenticated outbound-job status route:
curl -sS "https://api.mailagents.net/v1/outbound-jobs/$OUTBOUND_JOB_ID" \
-H "authorization: Bearer $TOKEN" | jqcurl -sS https://mailagents-dev.izhenghaocn.workers.dev/mcp \
-H 'content-type: application/json' \
-H "authorization: Bearer $TOKEN" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}' | jqImportant fields in tools/list:
annotations.riskLevelannotations.sideEffectingannotations.humanReviewRequiredannotations.supportsPartialAuthorizationannotations.sendAdditionalScopes
For composite tools:
requiredScopesmeans the minimum needed to use the tool at allsendAdditionalScopesmeans the extra scopes needed for delivery behavior
For operator agents using /admin/mcp, prefer workflow-pack discovery before
planning from raw admin tools alone:
npm run example:agent:discover-adminnpm run example:agent:admin-workflowsnpm run example:agent:operator-clientnpm run example:agent:admin-bootstrapnpm run example:agent:admin-reviewnpm run example:agent:admin-forensics- docs/admin-mcp.md
- docs/admin-workflow-packs.md
If the admin flow ends in a mailbox-scoped token, prefer the SDK handoff
through operator.openMailboxSession() so the rest of the workflow can stay on
the mailbox-first read/send/reply helpers.
Safe read-only sequence for inbound handling:
curl -sS https://mailagents-dev.izhenghaocn.workers.dev/mcp \
-H 'content-type: application/json' \
-H "authorization: Bearer $TOKEN" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_message",
"arguments": {
"messageId": "msg_demo_inbound"
}
}
}'Then optionally:
get_message_contentget_threadlist_agent_taskslist_messages
For mailbox-scoped agents that do not need explicit draft lifecycle control, you can now use the higher-level send routes directly:
curl -sS -X POST https://api.mailagents.net/v1/messages/send \
-H 'content-type: application/json' \
-H "authorization: Bearer $TOKEN" \
-d '{
"to": ["user@example.com"],
"subject": "Hello from Mailagents",
"text": "Sent through the high-level send route.",
"idempotencyKey": "sdk-send-001"
}' | jqReply to an inbound message in one request:
curl -sS -X POST https://api.mailagents.net/v1/messages/msg_demo_inbound/reply \
-H 'content-type: application/json' \
-H "authorization: Bearer $TOKEN" \
-d '{
"text": "Thanks for your message.",
"idempotencyKey": "sdk-reply-001"
}' | jqThe MCP equivalents are now available too:
curl -sS https://api.mailagents.net/mcp \
-H 'content-type: application/json' \
-H "authorization: Bearer $TOKEN" \
-d '{
"jsonrpc": "2.0",
"id": 2.5,
"method": "tools/call",
"params": {
"name": "send_email",
"arguments": {
"to": ["user@example.com"],
"subject": "Hello from MCP",
"text": "Sent through the MCP high-level send tool.",
"idempotencyKey": "sdk-mcp-send-001"
}
}
}' | jqcurl -sS https://api.mailagents.net/mcp \
-H 'content-type: application/json' \
-H "authorization: Bearer $TOKEN" \
-d '{
"jsonrpc": "2.0",
"id": 2.75,
"method": "tools/call",
"params": {
"name": "reply_to_message",
"arguments": {
"messageId": "msg_demo_inbound",
"text": "Thanks for your message.",
"idempotencyKey": "sdk-mcp-reply-001"
}
}
}' | jqList mailbox messages directly through MCP:
curl -sS https://api.mailagents.net/mcp \
-H 'content-type: application/json' \
-H "authorization: Bearer $TOKEN" \
-d '{
"jsonrpc": "2.0",
"id": 2.9,
"method": "tools/call",
"params": {
"name": "list_messages",
"arguments": {
"limit": 10,
"direction": "inbound"
}
}
}' | jqUse explicit draft creation only when your workflow needs a visible review step
or wants to control the draft lifecycle directly. For mailbox-scoped tokens,
create_draft can now infer mailbox context automatically, so you should not
pass agentId, tenantId, mailboxId, or from unless you are intentionally
using a broader operator token.
Create a draft directly:
curl -sS https://mailagents-dev.izhenghaocn.workers.dev/mcp \
-H 'content-type: application/json' \
-H "authorization: Bearer $TOKEN" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "create_draft",
"arguments": {
"to": ["user@example.com"],
"subject": "Hello from Mailagents",
"text": "Draft created through MCP."
}
}
}'Send it idempotently:
curl -sS https://mailagents-dev.izhenghaocn.workers.dev/mcp \
-H 'content-type: application/json' \
-H "authorization: Bearer $TOKEN" \
-d '{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "send_draft",
"arguments": {
"draftId": "REPLACE_WITH_DRAFT_ID",
"idempotencyKey": "sdk-send-001"
}
}
}'Cancel it if your workflow decides not to send:
curl -sS https://mailagents-dev.izhenghaocn.workers.dev/mcp \
-H 'content-type: application/json' \
-H "authorization: Bearer $TOKEN" \
-d '{
"jsonrpc": "2.0",
"id": 4.5,
"method": "tools/call",
"params": {
"name": "cancel_draft",
"arguments": {
"draftId": "REPLACE_WITH_DRAFT_ID"
}
}
}'Reply to inbound mail without sending:
curl -sS https://mailagents-dev.izhenghaocn.workers.dev/mcp \
-H 'content-type: application/json' \
-H "authorization: Bearer $TOKEN" \
-d '{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {
"name": "reply_to_inbound_email",
"arguments": {
"agentId": "agt_demo",
"messageId": "msg_demo_inbound",
"replyText": "Thanks for your message."
}
}
}'Reply and send with explicit idempotency:
curl -sS https://mailagents-dev.izhenghaocn.workers.dev/mcp \
-H 'content-type: application/json' \
-H "authorization: Bearer $TOKEN" \
-d '{
"jsonrpc": "2.0",
"id": 6,
"method": "tools/call",
"params": {
"name": "reply_to_inbound_email",
"arguments": {
"agentId": "agt_demo",
"messageId": "msg_demo_inbound",
"replyText": "Thanks for your message.",
"send": true,
"idempotencyKey": "sdk-reply-001"
}
}
}'Operator-guided manual send:
curl -sS https://mailagents-dev.izhenghaocn.workers.dev/mcp \
-H 'content-type: application/json' \
-H "authorization: Bearer $TOKEN" \
-d '{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "operator_manual_send",
"arguments": {
"agentId": "agt_demo",
"tenantId": "t_demo",
"mailboxId": "mbx_demo",
"from": "agent@mail.example.com",
"to": ["user@example.com"],
"subject": "Operator send",
"text": "Sent through operator_manual_send.",
"send": true,
"idempotencyKey": "sdk-manual-send-001"
}
}
}'Tool errors are returned in:
result.structuredContent.error.code
Example categories you can branch on safely:
- auth:
auth_unauthorized,auth_missing_scope - access:
access_tenant_denied,access_agent_denied,access_mailbox_denied - resource:
resource_message_not_found,resource_thread_not_found,resource_draft_not_found - idempotency:
idempotency_conflict,idempotency_in_progress
Suggested agent behavior:
- on
auth_missing_scope, stop and request broader authorization - on
access_mailbox_denied, stop and request mailbox-scoped approval - on
resource_*_not_found, stop and refresh identifiers - on
idempotency_in_progress, retry after a short delay - on
idempotency_conflict, treat it as a new logical request or investigate caller reuse
type CompatibilityContract = {
contract: { name: string; version: string; stability: string };
guarantees: { stableErrorCodes: string[] };
};
async function readCompatibility(baseUrl: string): Promise<CompatibilityContract> {
const response = await fetch(`${baseUrl}/v2/meta/compatibility`);
if (!response.ok) {
throw new Error(`Compatibility lookup failed: ${response.status}`);
}
return response.json() as Promise<CompatibilityContract>;
}
async function listTools(baseUrl: string, token: string) {
const response = await fetch(`${baseUrl}/mcp`, {
method: "POST",
headers: {
"content-type": "application/json",
authorization: `Bearer ${token}`,
},
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "tools/list",
params: {},
}),
});
const payload = await response.json();
return payload.result.tools;
}If you want a copyable wrapper instead of starting from raw fetch, see
docs/agent-client-helper.md.
If you want runnable repository examples, use:
npm run example:agent:discovernpm run example:agent:reply-draftnpm run example:agent:operator-send
For long-lived integrations:
- read
/v2/meta/compatibility - validate the response against
/v2/meta/compatibility/schema - obtain a least-privilege token, usually from the inline
accessTokenreturned byPOST /public/signup - call
tools/list - plan with
riskLevel,humanReviewRequired, andsendAdditionalScopes - prefer read calls before side effects
- prefer
list_messages,send_email, andreply_to_messagefor mailbox-scoped flows - use
idempotencyKeyon all retryable side-effecting sends or replays - branch only on stable error codes from the compatibility contract