An email function tool is an interface the model can request; it is not permission to access a mailbox. Your application decides which requests to execute and which data to return.
Mailera supplies the mailbox connection and email operations your server calls. For the broader architecture, see email for AI agents using existing mailboxes. Here, the important boundary is between reading a message, proposing a reply, and authorizing its delivery.
Should you use function tools or remote MCP?
Start with application-executed function tools. You can validate each request, restrict mailbox access, and keep the send credential outside the model's tool executor.
OpenAI defines function tools using JSON Schema, while application-side code executes the requested operation. The OpenAI function-calling guide describes that division of responsibility.
| Decision | Function tools | Remote MCP |
|---|---|---|
| Execution | Your application dispatches calls to Mailera REST endpoints | OpenAI invokes tools exposed by the remote server |
| Schema ownership | You define your wrapper's interface | The MCP server publishes its tools |
| Credentials | Your server holds the Mailera key | The remote connection needs its own authentication setup |
| Approval | Your application enforces the send boundary | Configure tool-call approval and check that it matches your application policy |
Mailera's MCP endpoint and setup information describe https://mailera.app/api/public/v1/mcp. Its tools include list_mailboxes, get_mailbox_status, list_messages, search_messages, get_message, get_latest_otp (read scope), and send_email, reply_to_email, get_send_status (send scope), plus move_message, rescue_spam (move scope) and sync_mailbox (sync scope).
OpenAI supports remote MCP servers using Streamable HTTP or HTTP/SSE. Its remote MCP guide documents allowed_tools, explicit approvals, and the mcp_list_tools discovery output.
Before choosing that route, check authentication, transport compatibility, discovery, filtering, and approval behavior together. A configuration copied from Claude, Cursor, or VS Code setup docs is not a substitute for those checks. The existing-mailbox MCP server guide explains that integration path in more detail.
What should you prepare first?
- Connect a mailbox you control. Open Mailboxes → Add mailbox, then follow the provider setup. For Outlook or Microsoft 365, use Mailera's Microsoft OAuth connection, rather than an app password. Confirm the mailbox's status on Mailboxes.
- Open Settings → API key tab → Create key. Choose a descriptive name, restrict Mailbox scope to the group containing your test mailbox, select Read, and set Expiration.
- Keep the OpenAI credential and Mailera read key on your server. Create a separate Mailera key with Send messages for the approved-send executor. Do not give that key to the read-tool process.

The key appears once under Key created — copy it now. The same dialog also has a Copy MCP config option if you later want to connect this key through MCP instead. Store it in your server's secret storage, not a prompt, browser bundle, or screenshot. The Mailera developer overview provides the REST integration entry point.

With $MAILERA_API_KEY set to your read key, inspect the mailbox list:
curl --fail-with-body \
'https://mailera.app/api/public/v1/mailboxes?limit=1' \
-H "Authorization: Bearer $MAILERA_API_KEY"The response contains a data array. Use the mailbox's id, status, and capabilities when setting up your application. A successful read does not establish that the connection can send; check capabilities.send separately.
How do you keep email tools narrowly scoped?
Use application-defined names that make the wrapper's purpose clear. These names are your interfaces, not native Mailera MCP tool names.
| Application wrapper | Mailera REST operation | Scope | Application restriction |
|---|---|---|---|
email_list_mailboxes | GET /mailboxes | read | Return only mailboxes authorized for the current user |
email_mailbox_status | GET /mailboxes/{id}/status | read | Bind the mailbox server-side before returning status |
email_list_inbox | GET /mailboxes/{id}/messages | read | Bind the mailbox server-side; fix the folder and cap results |
email_search_messages | GET /messages/search | read | Set the mailbox filter server-side to an authorized mailbox and cap results |
email_read_message | GET /mailboxes/{id}/messages/{msgId} | read | Check message ownership and cap content |
| Approved-send executor | POST /mailboxes/{id}/send | send | Require approval for the exact request payload |
GET /messages/search supports cross-mailbox search and is the REST counterpart to the MCP search_messages tool. The email_list_inbox wrapper instead uses the single-mailbox listing operation. For this walkthrough, keep search restricted to the authorized mailbox rather than exposing cross-mailbox access.
For a single-mailbox task, leave mailbox selection out of the model's arguments entirely. Resolve the mailbox from the authenticated user's application session.
For example, this application-owned input schema permits only a message identifier:
export const readMessageInput = {
type: "object",
properties: {
message_id: { type: "string", minLength: 1 }
},
required: ["message_id"],
additionalProperties: false
} as const;Use it when defining your function interface, following OpenAI's function-schema requirements. Independently validate the parsed arguments on your server; schema guidance to the model is not an authorization check.
export function parseReadArguments(raw: string): string {
const value: unknown = JSON.parse(raw);
if (!value || typeof value !== "object" || Array.isArray(value)) {
throw new Error("Expected an argument object");
}
const args = value as Record<string, unknown>;
if (Object.keys(args).length !== 1 ||
typeof args.message_id !== "string" ||
args.message_id.trim().length === 0) {
throw new Error("Expected only a nonempty message_id");
}
return args.message_id;
}The REST read itself uses the authorized mailbox and selected message:
curl --fail-with-body \
"https://mailera.app/api/public/v1/mailboxes/$MAILBOX_ID/messages/$MESSAGE_ID" \
-H "Authorization: Bearer $MAILERA_API_KEY"Before returning content to the model, verify that data.mailbox_id matches the authorized mailbox and data.id matches the requested message. Project only the fields needed for the task, such as subject, from, and a bounded portion of body.text. Do not forward the entire response by default.
How should your application run the tool loop?
Treat each model response as a set of requests to inspect, not a command to execute wholesale. OpenAI's function-calling flow can continue through multiple calls, so a dispatcher must handle more than the first requested operation.
- Submit the user's task with only the read-tool definitions.
- Inspect the returned tool calls and reject names outside your allowlist.
- Parse and validate each call's arguments. Resolve mailbox authorization from application state before making the Mailera request.
- Execute the read and turn its response into a bounded, serialized tool result. Track each dispatched read and its result in your application's own records; do not rely on execution completion order.
- Return the tool results to the model and continue the function-calling flow within your application's execution budget.
- Stop when the model returns its answer or your application's execution budget is exhausted.
Set budgets for calls, returned content, and elapsed work. If the model produces no tool calls, handle that as a normal answer rather than attempting another mailbox read.
For a tool failure, return a short application-generated error result without credentials or raw provider diagnostics. Do not represent a failed read as an empty inbox. When reads finish out of order, update the corresponding read records in your application's task state rather than assigning results by completion order.
Store a proposed reply in your application's state. It remains a proposal until a person approves it; generating text does not require giving the model send access.

How do you approve and send without duplicates?
Make the review screen show the sender mailbox, every recipient, subject, complete body, and reply target. Avoid a generic approval button that authorizes whatever the model produces next. For the broader approval architecture, see building an email agent with approval boundaries.
Use this application-side sequence:
- Build and persist the exact proposed send payload, including its reply target.
- Show that immutable version to the reviewer.
- Record the authenticated reviewer's approval against that version or a digest of its complete payload.
- Have the send executor load the stored payload and approval. Recheck mailbox authorization and reject any mismatch.
- Claim the approved operation atomically before dispatch so competing workers cannot independently submit it.
Any edit requires new approval, including a recipient change or a different sending mailbox. Never accept an approved flag supplied by the model as proof of human consent. This approval workflow belongs to your application, not to a native Mailera approval mechanism.
Mailera supports sending, replying to, or forwarding a message through POST /mailboxes/{id}/send, using the send scope. Build the request according to the Mailera API documentation, then bind approval to that exact request.
Persist an Idempotency-Key before the first submission. Reuse it for retries of the same operation with the same payload. Mailera's configured idempotency window is 24 hours; keep your own operation record so a retried job outside that window doesn't get treated as a brand-new send. See preventing duplicate sends with Idempotency-Key.
Record the send-attempt identifier and inspect GET /sends/{id} with the send credential. Keep queued, sending, sent, failed, and unknown distinct. In particular, an unknown outcome calls for reconciliation, not a new key and another send.
What commonly breaks?
| Symptom | What to inspect |
|---|---|
| Arguments fail validation | Reject malformed JSON, unexpected fields, and invalid values before dispatch |
| A result appears attached to the wrong read in application records | Track each dispatched read and its result in your application's own records; do not match by completion order |
| Your application repeats completed reads | Check its task records for completed, pending, and failed reads before dispatching more work |
| Only one requested read runs | Iterate over all returned calls within your execution budget |
| Mailera rejects a read | Check Read permission, mailbox-group restrictions, and whether the key was revoked |
| Messages look stale | Check mailbox status and synchronization timestamps |
| Reads work but sending does not | Check the separate send key and capabilities.send; the connection may be receive-only |
| Requests are rate-limited | Use bounded retries with backoff; do not continuously replay the whole task |
| Remote MCP tools do not appear | Diagnose authentication, transport, discovery, and filtering separately from REST wrappers |
Mailbox freshness depends on provider connectivity and synchronization. Background checks vary with mailbox activity, and idle mailboxes can sleep. Do not design the workflow around an absolute real-time arrival assumption.
How do you keep incoming email from becoming instructions?
Treat subjects, bodies, attachments, and quoted replies as untrusted data. A message asking the agent to reveal a key, change its tools, or forward unrelated correspondence is still message content, not authorization.
Keep credentials outside model context. Send only the content needed for the user's task and redact sensitive content from logs. The read dispatcher should expose no import, sharing, management, or OTP tools.
Before enabling sending, test that malicious message content cannot cause a send, change recipients after approval, or read another user's mailbox. Also test simultaneous workers retrying the same approved operation. These are application tests, not behaviors to enforce only through a system prompt.
For the access review, use Mailera's security information alongside the guide to connecting an AI assistant to email safely. The MCP tools specification also recommends confirmation for sensitive operations.
What does Mailera not do here?
Mailera connects mailboxes you already have; it does not create new mailboxes or addresses. It does not provide bulk or marketing sending, and a sent status is not a guarantee of inbox placement.
Your application remains responsible for deciding who may read each mailbox, binding approval to the exact reply, and preventing its workers from dispatching an operation twice.
Let AI agents use your mailbox via MCP
Add Mailera's MCP server to Claude, Cursor or VS Code so agents can read, categorize and reply to email messages.
Learn how to use MCP








