MailLane / docs

Openclaw integration guide

Set up MailLane with Openclaw using pull mode or webhook mode.

Intro

MailLane accepts allowlisted inbound email and hands accepted messages to Openclaw through pull mode or webhook mode.

If you are just getting started, use pull mode. It is the simplest setup and the recommended default. If you already run a public Openclaw hook endpoint, you can switch to webhook mode later.

If you are wiring this up with an agent runtime, jump to For agents for the canonical machine-readable setup manifests.

Choose your path

  • Pull mode (recommended): Openclaw polls MailLane for pending accepted messages.
  • Push mode / webhook mode (advanced): MailLane pushes accepted messages into your Openclaw /hooks/agent endpoint.

Prerequisites

  • a MailLane account and a provisioned inbox
  • the inbox id you want Openclaw to read from or receive pushes for
  • a pull token for pull mode or an Openclaw hook token for push mode
  • an allowlist that already matches the senders you expect
  • for push mode, a public Openclaw URL that ends in /hooks/agent

Important setup advice

If you already have a normal mailbox such as Gmail or Outlook, it is usually better to treat your MailLane address as a forwarding target from that main inbox instead of making MailLane the primary mailbox address for your bot. That keeps provider-specific mailbox workflows, login recovery, and human-facing email in your normal inbox while MailLane stays a narrow relay into Openclaw.

Before you enable forwarding:

  • make sure Openclaw is already ready to process MailLane traffic
  • confirm the senders you want to relay are on the inbox allowlist
  • do not add a catch-all allowlist rule just to "make it work" first. A broad rule such as .* or an all-allowed entry removes MailLane's main security boundary and is strongly discouraged.

Some mailbox providers send a forwarding confirmation email before they begin relaying mail. Gmail does this. That confirmation message lands at MailLane first. If your bot is not explicitly configured to notice and handle that message, check the inbox's rejected email diagnostics list first so you can find the confirmation email and complete the provider's manual confirmation step.

Current functional limits

MailLane's current hosted defaults and customer-visible caps include:

  • public routes currently use a 60-request / 60-second rate-limit window, so pull mode should poll on a modest interval instead of a tight loop
  • pull mode keeps only the newest 10 pending accepted messages per inbox; when older pending messages were dropped, droppedCount reports how many and overflowed becomes true
  • after a successful pull, MailLane keeps a 24-hour duplicate-suppression record for each consumed message, so provider retries during that window do not cause the same message to reappear as pending
  • rejected email diagnostics keep only the newest 10 rejected emails per inbox when retention is enabled, and retention is on by default for new inboxes
  • MailLane runs an antivirus and malware check on inbound mail. If MailLane detects a virus or malware, it rejects the message before allowlist acceptance or downstream delivery, and the rejected-email entry keeps metadata only instead of parsed body or thread content

MailLane exposes pending accepted messages at GET /v1/inboxes/:inboxId/pending. Send Authorization: Bearer <pull-token> with each request.

Each successful pull returns the current pending messages and hard-deletes the returned messages from MailLane.

After a successful pull, MailLane keeps a short 24-hour duplicate-suppression record for each consumed message. If your inbox provider retries the same email during that window, MailLane suppresses the duplicate instead of making that already-consumed message pending again.

Pull and push use the same accepted message object. Pull returns it directly in messages[]. Push embeds the same JSON inside the Openclaw hook payload's message field.

Pull example request

curl -sS \
  -H "Authorization: Bearer <pull-token>" \
  https://maillane.dev/v1/inboxes/<inbox-id>/pending

Pull example response

{
  "droppedCount": 0,
  "messages": [
    {
      "allowlistMatch": {
        "ruleType": "exact_email",
        "ruleValue": "alerts@example.com"
      },
      "id": "msg_123",
      "message": {
        "bodyText": "Build complete for deploy 123.",
        "cc": [
          {
            "email": "ops@example.com",
            "name": "Ops"
          }
        ],
        "from": {
          "email": "alerts@example.com",
          "name": "Build Bot"
        },
        "replyTo": [],
        "sentAt": "2026-03-27T11:59:30.000Z",
        "subject": "Build complete",
        "thread": {
          "inReplyTo": null,
          "references": []
        },
        "to": [
          {
            "email": "agent@agent.maillane.dev",
            "name": "Agent Inbox"
          }
        ]
      },
      "occurredAt": "2026-03-27T12:00:00.000Z"
    }
  ],
  "overflowed": false
}

Pull response keys

  • droppedCount: how many older accepted messages MailLane dropped before this response because the inbox exceeded the pull retention cap
  • messages: the accepted messages returned by this pull
  • overflowed: true when one or more older accepted messages were dropped

Accepted message keys

  • allowlistMatch: the allowlist rule that accepted the sender
  • allowlistMatch.ruleType: the rule kind that matched, such as exact_email
  • allowlistMatch.ruleValue: the configured rule value that matched
  • id: MailLane's durable id for the accepted message
  • message: the normalized inbound email content
  • message.bodyText: the plain-text body extracted from the inbound email
  • message.cc: the normalized CC recipients
  • message.from: the normalized sender
  • message.replyTo: the normalized Reply-To recipients
  • message.sentAt: when the upstream email says it was sent
  • message.subject: the normalized subject line
  • message.thread: threading metadata
  • message.thread.inReplyTo: the upstream parent reference when present, or null
  • message.thread.references: the upstream reference chain in order
  • message.to: the normalized inbox recipients
  • occurredAt: when MailLane accepted the message

Participant keys

  • email: the normalized email address
  • name: the optional display name when one was present in the inbound message

Minimum fields to process first

For a first working Openclaw flow, extract these fields from each pulled message:

  • id for MailLane message tracking
  • message.subject and message.from.email for quick routing logic
  • message.bodyText for the email content you want Openclaw to process
  • occurredAt for ordering or freshness checks
  • allowlistMatch if you want to branch on which allowlist rule accepted it

Push mode (webhook, advanced)

Use push mode only when your Openclaw instance already exposes a public /hooks/agent endpoint and you want MailLane to push accepted messages immediately.

  • the target must end with /hooks/agent
  • MailLane sends Authorization: Bearer <openclaw-hook-token>
  • MailLane sends Content-Type: application/json
  • secure HTTPS is the default expectation
  • insecure HTTP is testing-only
  • localhost and private-network targets are rejected

Use insecure HTTP only for temporary testing on systems you control. MailLane still rejects localhost and private-network webhook targets even in testing mode.

Webhook example payload

{
  "deliver": false,
  "message": "A new MailLane inbound email was accepted.\n\n{\"allowlistMatch\":{\"ruleType\":\"exact_email\",\"ruleValue\":\"alerts@example.com\"},\"id\":\"msg_123\",\"message\":{\"bodyText\":\"Build complete for deploy 123.\",\"cc\":[],\"from\":{\"email\":\"alerts@example.com\",\"name\":\"Build Bot\"},\"replyTo\":[],\"sentAt\":\"2026-03-27T11:59:30.000Z\",\"subject\":\"Build complete\",\"thread\":{\"inReplyTo\":null,\"references\":[]},\"to\":[{\"email\":\"agent@agent.maillane.dev\",\"name\":\"Agent Inbox\"}]},\"occurredAt\":\"2026-03-27T12:00:00.000Z\"}",
  "name": "MailLane",
  "wakeMode": "now"
}

The JSON after the blank line inside message is the same accepted message object described in the pull section above.

Push payload keys

  • deliver: the Openclaw hook flag MailLane sets to false
  • message: a short MailLane notice followed by a blank line and the accepted message JSON
  • name: the sender label, always MailLane
  • wakeMode: the Openclaw wake hint, currently now

For agents

These setup manifests are intentionally machine-readable and can be consumed as application/json.

Pull manifest

{
  "mode": "pull",
  "recommended_for": "beginner",
  "maillane_endpoint": "GET /v1/inboxes/:inboxId/pending",
  "http_method": "GET",
  "auth_scheme": "Authorization: Bearer <pull-token>",
  "required_fields": [
    "inboxId",
    "pullToken"
  ],
  "request_example": {
    "headers": {
      "Authorization": "Bearer <pull-token>"
    },
    "method": "GET",
    "url": "https://maillane.dev/v1/inboxes/<inbox-id>/pending"
  },
  "response_example": {
    "droppedCount": 0,
    "messages": [
      {
        "id": "msg_123",
        "allowlistMatch": {
          "ruleType": "exact_email",
          "ruleValue": "alerts@example.com"
        },
        "message": {
          "bodyText": "Build complete for deploy 123.",
          "cc": [],
          "from": {
            "email": "alerts@example.com",
            "name": "Build Bot"
          },
          "replyTo": [],
          "sentAt": "2026-03-27T11:59:30.000Z",
          "subject": "Build complete",
          "thread": {
            "inReplyTo": null,
            "references": []
          },
          "to": [
            {
              "email": "agent@agent.maillane.dev",
              "name": "Agent Inbox"
            }
          ]
        },
        "occurredAt": "2026-03-27T12:00:00.000Z"
      }
    ],
    "overflowed": false
  },
  "verification_steps": [
    "Call the pending endpoint with the inbox id and pull token.",
    "Confirm the response includes a messages array.",
    "Confirm successful pulls hard-delete returned accepted messages."
  ],
  "constraints": [
    "Polling must be enabled for the inbox.",
    "Messages are removed from MailLane after a successful pull.",
    "Public routes currently use a 60-request / 60-second rate-limit window.",
    "After a successful pull, MailLane keeps a 24-hour duplicate-suppression record for consumed messages so provider retries in that window do not reappear as pending.",
    "Only the newest 10 pending accepted messages are retained per inbox; older ones increment droppedCount and set overflowed=true."
  ]
}

Webhook manifest

{
  "mode": "webhook",
  "recommended_for": "advanced",
  "maillane_endpoint": "POST <public-openclaw-url>/hooks/agent",
  "target_path": "/hooks/agent",
  "http_method": "POST",
  "auth_scheme": "Authorization: Bearer <openclaw-hook-token>",
  "content_type": "application/json",
  "required_fields": [
    "targetUrl",
    "openclawHookToken"
  ],
  "request_example": {
    "headers": {
      "Authorization": "Bearer <openclaw-hook-token>",
      "Content-Type": "application/json"
    },
    "method": "POST",
    "url": "https://openclaw.example.com/hooks/agent"
  },
  "payload_example": {
    "deliver": false,
    "message": "A new MailLane inbound email was accepted.\n\n{\"allowlistMatch\":{\"ruleType\":\"exact_email\",\"ruleValue\":\"alerts@example.com\"},\"id\":\"msg_123\",\"message\":{\"bodyText\":\"Build complete for deploy 123.\",\"cc\":[],\"from\":{\"email\":\"alerts@example.com\",\"name\":\"Build Bot\"},\"replyTo\":[],\"sentAt\":\"2026-03-27T11:59:30.000Z\",\"subject\":\"Build complete\",\"thread\":{\"inReplyTo\":null,\"references\":[]},\"to\":[{\"email\":\"agent@agent.maillane.dev\",\"name\":\"Agent Inbox\"}]},\"occurredAt\":\"2026-03-27T12:00:00.000Z\"}",
    "name": "MailLane",
    "wakeMode": "now"
  },
  "verification_steps": [
    "Configure a public Openclaw hook target ending in /hooks/agent.",
    "Store the bearer token in MailLane.",
    "Send an allowlisted test message and confirm Openclaw receives the payload."
  ],
  "constraints": [
    "HTTPS is recommended.",
    "Insecure HTTP is testing-only.",
    "Localhost and private-network targets are rejected."
  ]
}

Verification

  1. Send one allowlisted email into the MailLane inbox.
  2. For pull mode, confirm Openclaw can fetch the pending message from GET /v1/inboxes/:inboxId/pending.
  3. For webhook mode, confirm MailLane accepts the public /hooks/agent target and Openclaw receives the payload.
  4. Confirm the payload shape matches your expected workflow input.

Troubleshooting

  • 401 unauthorized: the pull token or webhook bearer token is missing or wrong
  • 429 from the pending endpoint: back off and poll less aggressively; the current hosted default is a 60-request / 60-second rate-limit window
  • 409 polling_not_enabled: the inbox is not in pull mode
  • invalid webhook target: the target is not public or does not end with /hooks/agent
  • no delivery: confirm the sender matches the inbox allowlist before changing anything else
  • forwarding never starts from Gmail or another mailbox provider: check the rejected email diagnostics list for a forwarding-confirmation email that still needs manual approval