Documentation menu

Security & revoking access

Tokens, not passwords

Connecting an assistant never hands it your MailInApp password or an API key. Approving the consent screen mints an OAuth access token scoped to exactly the permissions you checked, tied to exactly the account you selected. Tokens are opaque random strings stored hashed — the same pattern MailInApp already uses for developer API keys — so a database read can't recover a usable credential, and revoking one is instant rather than waiting for an expiry.

  • Access tokens last 1 hour.
  • Refresh tokens last 60 days and rotate on every use — the old one stops working the moment a new one is issued, and if a since-rotated refresh token is ever replayed (a sign it leaked), the whole connection is revoked automatically rather than just that one token.

Two-step confirmation before any send

campaigns:send being granted doesn't mean an assistant can email your contacts in one step. Sending always goes through two separate tool calls:

  1. prepare_send resolves your audience, applies suppressions, and renders a real preview — annotated read-only, because it sends nothing. It returns the recipient count, a sample of addresses, the rendered HTML, and a short-lived confirmationToken bound to that exact project, subject and audience.
  2. confirm_send requires that exact token, plus the exact same project/subject/audience it was minted for. Any mismatch — a different subject, a different row selection, an expired token (10-minute window) — is rejected outright.

The practical effect: an assistant cannot send mail without first surfacing what it's about to send and to how many people, and it cannot swap the audience or subject between showing you the preview and actually sending. On Claude and ChatGPT, prepare_send's result renders as an actual card with its own Confirm & Send button — so the human approval is a click on a rendered preview, not a sentence in a chat transcript a model could talk itself past.

Every send made this way still passes through your account's existing safeguards on top: your plan's connectorSendRecipientsMonthly cap (separate from, and much lower than, your regular monthly send volume — 0 on Free), the same suppression list every other send checks, and your account's overall monthly send cap. Each one is also logged as its own audit record.

Untrusted content is fenced, not trusted

Contact fields, inbound mailbox message bodies, and form free-text answers are all written by people other than you — a recipient, or whoever's emailing your support inbox. Tools that return this content wrap it clearly as untrusted data in the tool result, so your assistant treats it as something to read and summarize, never as an instruction to follow. (For the technically curious: this fencing lives in the tool result, never the tool description — putting behavioral instructions in a description is exactly the kind of prompt-injection surface connector reviewers reject on sight.)

What no tool can ever reach

No connector tool — regardless of which scopes you grant — can read or rotate an API key, a webhook signing secret, an SMTP password, or an OAuth client secret for a connected data source; change payment, domain, or SMTP settings; or manage team invites. These mirror the exact carve-outs an editor teammate already can't reach anywhere else in MailInApp. Nothing here touches Stripe or billing either — a disputed charge or a refund stays a dashboard-only action.

Which account, always re-checked

A connection is bound to one account at consent time and never re-reads a header to decide which account to act as (unlike a signed-in dashboard session choosing between companies). That binding is re-validated on every single call against your current team membership — if you're removed from a team, or your role changes, the very next call on that connection reflects the new reality, not what was true when you granted it.

Revoking a connection

Go to Connections in the dashboard (/dashboard/connections). Every assistant you've approved is listed with its granted scopes and when it last made a call; Revoke takes effect immediately — every token that assistant is holding, access and refresh alike, stops working on its very next call. There's nothing to undo on the platform side (Claude/ChatGPT/Gemini) first; revoking here is always sufficient.