Docs › Integrations

FairMail Agent

Connect a user's Gmail once, then read their mail, send on their behalf, and receive new-email webhooks — all through Fair's API. Any platform (Launched, a portal, your own app) integrates the same way. The email provider is never exposed; you only ever call Fair endpoints and see Fair-shaped IDs.

Getting started

You need a project-scoped Fair API key from tryfair.ai/api-keys. Every request is authenticated with that key plus a user_ref — your end-user's stable identifier.

Base URL (prod): https://api.faircompany.ai
Base URL (staging): https://staging.api.faircompany.ai

Authentication

Send your Fair API key as a Bearer token on every request:

Authorization: Bearer <FAIR_API_KEY>
  • user_ref — your end-user's stable identifier. Fair namespaces each connection per (workspace, user_ref), so one tenant can never read another's mail.
  • workspaceId — only required if a key owns multiple mail workspaces. Pass ?workspaceId=… or the X-Workspace-Id header. Single-workspace keys can omit it.

The end-to-end flow

1. POST /v1/mail/agent/connect         → { connect_url, connection_id }
2. Redirect the user to connect_url    → they approve Google consent
3. GET  /v1/mail/agent/connection/:id  → poll until { status: "ACTIVE" }
4. GET  /v1/mail/agent/messages        → read their mail
   POST /v1/mail/agent/send            → send on their behalf
5. POST /v1/mail/agent/triggers        → subscribe; Fair POSTs new_email to you

A connection is PENDING until the user finishes Google consent, then ACTIVE. Reading or sending before it's ACTIVE returns 409 auth_required — surface "connect your inbox to continue".

Endpoints

Method Path What it does
POST /v1/mail/agent/connect Start (or idempotently reuse) a connection
GET /v1/mail/agent/connection/:id Poll connection status
GET /v1/mail/agent/messages Read a connected user's mail
POST /v1/mail/agent/send Send on the user's behalf (billable)
POST /v1/mail/agent/triggers Subscribe to new-email webhooks
your callback_url Fair POSTs new_email events to you

1. Start a connection

curl -X POST https://api.faircompany.ai/v1/mail/agent/connect \
  -H "Authorization: Bearer $FAIR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user_ref": "user_123" }'
# New, or a not-yet-active connection (needs OAuth):
201 { "connect_url": "https://connect.composio.dev/link/…",
      "connection_id": "magc_…", "status": "PENDING" }

# Already-connected user_ref — idempotent no-op (no re-auth, no connect_url):
200 { "connection_id": "magc_…", "status": "ACTIVE" }

Redirect the user's browser to connect_url when one is returned. Re-calling connect for the same user_ref is idempotent and safe: an already-ACTIVE mailbox returns 200 with the existing connection_id and does not re-link. Branch on status / the presence of connect_url, not the HTTP code alone.

2. Check connection status

curl https://api.faircompany.ai/v1/mail/agent/connection/magc_123 \
  -H "Authorization: Bearer $FAIR_API_KEY"
200 { "status": "PENDING" }   // user hasn't finished consent yet
200 { "status": "ACTIVE" }    // ready to read/send
200 { "status": "EXPIRED" }   // link lapsed — call connect again

Poll after redirecting the user, or drive it from your own post-OAuth return.

3. Read messages

curl "https://api.faircompany.ai/v1/mail/agent/messages?user_ref=user_123&limit=20" \
  -H "Authorization: Bearer $FAIR_API_KEY"
200 {
  "messages": [
    { "id": "19f4…", "thread_id": "19f4…", "from": "Someone <a@b.com>",
      "to": "user@their-inbox.com", "subject": "…",
      "preview": "first line of the body…",
      "timestamp": "2026-07-09T23:19:22Z",
      "labels": ["INBOX", "UNREAD"] }
  ]
}

Reads pull metadata + preview and are effectively free — fetch full bodies only when you need them.

4. Send an email billable — cost-plus per send

curl -X POST https://api.faircompany.ai/v1/mail/agent/send \
  -H "Authorization: Bearer $FAIR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user_ref": "user_123", "to": "x@y.com", "subject": "Hi", "body": "…" }'
200 { "message_id": "19f4…" }

Billing is charged before the send: an unfunded wallet is rejected with 402 and no email goes out; a funded wallet is charged and the mail is sent.

5. Subscribe to new-email events

curl -X POST https://api.faircompany.ai/v1/mail/agent/triggers \
  -H "Authorization: Bearer $FAIR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user_ref": "user_123",
        "callback_url": "https://yourapp.com/webhooks/fair-mail" }'
201 { "trigger_id": "…" }

6. Receive new-email webhooks

Event-driven — Fair delivers one POST per new message to your callback_url, deduped by message id (never a full re-pull):

POST {your callback_url}
{ "event": "new_email", "connection_id": "magc_…", "user_ref": "user_123",
  "message": { "id": "…", "from": "…", "subject": "…",
               "preview": "…", "timestamp": "…" } }

Respond 2xx to acknowledge. Analyze once per message id and gate expensive processing (e.g. AI) behind your own rules, so you only pay for the mail that matters.

Latency caveat. New-email webhooks are driven by Gmail's push channel and typically arrive within ~15 minutes of the message landing — this is an upstream Gmail characteristic, not a Fair queue delay. For time-critical reads, poll GET /messages in addition to relying on the webhook.

SDK & MCP

The same surface is available in the typed @fair/sdk under mail.agent.*:

import { FairClient } from "@fair/sdk";

const fair = new FairClient({ apiKey: process.env.FAIR_API_KEY });

// 1. Connect a user's Gmail once
const { connect_url, connection_id } = await fair.mail.agent.connect({
  user_ref: "user_123",
});

// 2. Read their mail
const { messages } = await fair.mail.agent.messages({
  user_ref: "user_123",
  limit: 20,
});

// 3. Send on their behalf (billable — cost-plus per send)
const { message_id } = await fair.mail.agent.send({
  user_ref: "user_123",
  to: "x@y.com",
  subject: "Hi",
  body: "…",
});

// 4. Subscribe to new-email webhooks
await fair.mail.agent.subscribeTrigger({
  user_ref: "user_123",
  callback_url: "https://yourapp.com/webhooks/fair-mail",
});

The agent surface is also exposed as tools on the unified Fair MCP server, so an AI agent can connect a mailbox, read, and send using the same endpoints described here.

Error reference

Status Code Meaning
400 fair/validation-error Bad/missing field, or multiple workspaces — pass workspaceId
402 wallet/insufficient-balance Wallet empty; top up. (Charged before send — no mail sent.)
404 mail/agent-connection-not-found Unknown connection_id (or not in your workspace)
409 mail/agent-not-connected No connection for this user_ref — call connect first
409 mail/agent-auth-required Connection exists but still PENDING — finish consent
4xx connector/* Upstream provider issue, surfaced brand-clean (no internal leak)

Full API reference

Every endpoint, schema, and response is in the live Swagger reference: api.faircompany.ai/v1/docs/ui. See also the Fair API overview and the Connections (Slack) guide.

Support

Questions? Email hello@faircompany.ai.