What do you need before you start?

Build the first version around one message, not an unattended inbox. An email AI agent here is an application that reads email and uses a model to propose a response. Sending remains a separate, authorized action.

Use a mailbox you own, a second address you control, a server-side application, and a model API. Send a synthetic message such as “Please acknowledge receipt of the test checklist.” Keep customer data out of this run and credentials outside source code.

Mailera connects existing mailboxes. Divide responsibility explicitly:

ResponsibilityMaileraYour application
Connect the mailboxProvider connectionChoose the authorized mailbox
Read emailREST message accessSelect and minimize content
Generate a reply—Call the model and store its proposal
Approve the reply—Review interface and payload validation
Send and inspect statusSend and status endpointsApproval enforcement and retry state

Use REST for this workflow because separate read and send requests make the authorization boundary visible. The Mailera developer overview and email API guide cover the API orientation. If your application uses MCP instead, see the MCP tool catalogue. Pick one path for the first prototype rather than implementing both at once.

The broader existing-mailbox approach to email agents explains where this workflow fits.

Email flows from an existing mailbox through read-only access and a model proposal to human approval, then a separate sender checks the send outcome.
The model proposes text. Application code controls whether that exact reply can be sent.

Step 1: Connect and restrict the mailbox

For an Outlook email AI agent, open Mailboxes → Add mailbox (Admin or Leader role required) and choose the Sign in with Microsoft tab. Use the first-mailbox setup guide for the connection steps and the Microsoft provider page for that connection path.

Mailera connects Outlook, Hotmail, and Microsoft 365 through OAuth or an imported refresh token, not an app password. For background on the underlying flow, see Microsoft's delegated authorization documentation, which explains the access-token flow used to call Microsoft Graph.

If you use Gmail instead, Mailera's connection uses an app password. Google's app-password requirements include having 2-Step Verification enabled on your account, and Google recommends using "Sign in with Google" instead when an app offers it.

Create a mailbox group containing only your test mailbox. Then open Settings → API key tab → Create key, choose that group under Mailbox scope, and enable only Read. Copy the key into your server-side secret store when it appears; Mailera shows it only once.

Create a separate key with Send messages for the trusted sending component. Do not give that credential to the model or reading worker. Group scope limits mailbox access; it does not authorize a particular reply.

Mailera API key dialog scoped to a test mailbox group with Read enabled and Send messages disabled.
Scope the reading key to the test group and leave Send messages disabled.

Load the reading key into $MAILERA_API_KEY through your environment, then list accessible mailboxes:

curl --fail-with-body \
  'https://mailera.app/api/public/v1/mailboxes?limit=1' \
  -H "Authorization: Bearer $MAILERA_API_KEY"

Inspect data[].id, email, group_id, status, and capabilities.send. Confirm the returned mailbox is yours, then set $MAILBOX_ID to its id. A successful read is not proof that sending is configured: a mailbox saved with Save as receive-only needs its sending configuration fixed before the reply step.

Step 2: Read one message and propose a reply

  1. Send the synthetic message from your second controlled address.
  2. List messages in the connected mailbox's Inbox.
  3. Select the test message explicitly by sender and subject. Don't automatically act on whichever message happens to be newest.
curl --fail-with-body \
  "https://mailera.app/api/public/v1/mailboxes/$MAILBOX_ID/messages?folder=inbox&limit=10" \
  -H "Authorization: Bearer $MAILERA_API_KEY"

The response contains a data array. Use each message's id, from.address, and subject to identify your test message. Store its mailbox_id and id in application state, and set $MESSAGE_ID to the selected message's id.

Retrieve its content:

curl --fail-with-body \
  "https://mailera.app/api/public/v1/mailboxes/$MAILBOX_ID/messages/$MESSAGE_ID" \
  -H "Authorization: Bearer $MAILERA_API_KEY"

Read data.body.text for the plain-text content. Check data.body_error; stop for review if the content is missing or cannot be read. Don't generate a reply from preview alone when the full message is needed.

Pass only the necessary subject and message text to your model. Keep email content separate from application instructions. Ask for a proposed reply body, not a tool call or permission decision. For this test, an appropriate response would acknowledge the checklist without inventing commitments.

Store the proposal in application memory or application storage alongside the source identifiers. This is an application-held proposal, not a provider-side draft. Choose the recipient in application code from your controlled-address allowlist; the model should not decide where mail may be sent.

Step 3: Approve, send, and inspect the outcome

Show the reviewer the source message, sending mailbox, proposed recipient, subject, and full reply body. Make rejection an explicit outcome that leaves the action unsent.

Bind approval to the exact outgoing payload. One implementation is to store an immutable payload version with the reviewer's decision. If anyone edits the recipient, content, or other outgoing fields, require approval again.

Before submission, the trusted sending component should:

  1. Validate every recipient against the test-address allowlist and reject attachments for this first version.
  2. Verify that the approved payload version matches the payload it will submit.
  3. Persist the logical action identity, approved payload, and idempotency key.
  4. Claim that action atomically so concurrent workers cannot both start it.
  5. Submit through POST /mailboxes/{id}/send using the separate Send messages key and the Idempotency-Key header.

Use the reply request format in the Mailera API documentation. Keep approval enforcement outside the model: neither a helpful-looking reply nor instructions inside an email can authorize sending.

Mailera's idempotency window is 24 hours. Reuse the persisted key and unchanged payload when retrying the same action within that window. Do not generate a fresh key merely because the HTTP request timed out, and do not treat a retry after the window as protected by the original key. The duplicate-send guide covers this boundary in more detail.

Implement send-response parsing against the send endpoint documentation, and store the send-attempt identifier alongside the persisted action. Use that identifier, not $MESSAGE_ID or the idempotency key, as {id} in GET /sends/{id}. The status request requires a key with the send scope.

StatusApplication handling
queuedKeep the action pending; don't submit another reply.
sendingContinue checking this attempt.
sentRecord completion; inspect the controlled recipient mailbox.
failedInvestigate the failure before deciding on another attempt.
unknownPause automatic sending and reconcile the existing attempt.

A sent status is not an inbox-placement guarantee; filtering decisions belong to the receiving provider, not to this workflow. If a timeout leaves you without a send identifier, preserve the action and idempotency key rather than starting a new action.

What commonly goes wrong, and how do you test it?

Check mailbox access before debugging the model. A missing message and a failed proposal are different problems.

SymptomCheckSafe next action
Test mailbox is absentKey's mailbox groupCorrect the group scope; don't broaden it to all groups.
A request is deniedRequired permissionKeep Read and Send messages separate; fix the relevant component.
Mailbox is disconnectedstatus, error_code, and errorRestore the connection before continuing.
Test message is absentSelected folder and sync informationWait or use an authorized sync path; don't invent message content.
Reads work, sends don'tcapabilities.send and receive-only setupRepair sending configuration before approval.
Send times out or returns unknownStored action, key, and send attemptReconcile the existing attempt; don't blindly resend.

Run these acceptance tests against controlled addresses:

TestRequired observation
Ordinary checklist messageProposal appears; nothing sends before approval; approved reply goes to the expected recipient.
Email asks for secrets or forwardingNo secret is disclosed and no new recipient is authorized.
Reviewer rejects the proposalNo send request occurs.
Payload changes after approvalPrevious approval is rejected.
Same approved action executes againThe application reuses its action state; the tested retry path produces no duplicate reply.

Also exercise the send path with the Read-only key and confirm it is denied. Attempt access to a controlled mailbox outside the scoped group and confirm it is excluded. These are release checks, not reasons to give the reading worker more permissions.

For continuous processing, persist next_cursor from message listings and supply it as cursor for subsequent pages. Maintain a processed-message ledger keyed by mailbox and message identity, with a unique logical reply action. Commit processing state before advancing past work you still need to perform. Add webhook triggering later, with signature verification and delivery deduplication.

How do you keep incoming email from controlling the agent?

Incoming email is untrusted data, not instructions. That includes quoted text, links, and attachments, even when the apparent sender is familiar.

Enforce permissions and recipient policy in application code. A prompt saying “never send without approval” is not an authorization boundary. The model should have neither the sending credential nor a route around the approval check.

Exclude attachment processing and automatic link fetching from the first version. Never let email instructions disclose secrets, change tool permissions, fetch arbitrary URLs, or forward unrelated messages. Reject those actions rather than asking the model whether they seem reasonable.

Keep credentials out of prompts and redact unnecessary message content from logs. Review Mailera's security information alongside your own application's controls.

Provide an operational stop: pause the application worker, then revoke its API keys if access must end. Mailera rejects calls immediately after key revocation. Preserve action records for review so restarting the worker does not recreate old replies as new work.

What does Mailera not handle for this agent?

Mailera connects existing mailboxes; it does not create email addresses. The model runtime, human review interface, and durable workflow state in this design are components you build. Application policy enforcement belongs there too.

Mailera does not provide bulk marketing sending or mailbox warmup, and it is not an official partner of Microsoft, Google, or any other mailbox provider. Do not design around absolute real-time receipt or automatic recovery from every provider failure. Proton Mail and Tuta are not supported by these connection options.

Connect your first mailbox in 2 minutes

Add an Outlook, Gmail or IMAP mailbox and read email messages in the app or via the API.

Connect mailbox