Search the docs

Find a page, a section, or an endpoint.

Make

Triggers for mail and hand-offs, modules, and two templates.

Give a Make scenario an email address of its own. Mail that arrives starts the scenario; the scenario replies, saves drafts for a person to send, hands threads to people and waits for verification codes. Nobody's own inbox is connected to Make, and a model in the scenario only ever reads the one message in front of it.

The app is not in Make yet. It is built and run against production every day, and is waiting to be published in Make. Until it is, each module below names the one endpoint it calls, so Make's own HTTP → Make a request module with your key in an Authorization: Bearer header does the same.

The connection

Make a key at Developers → API keys, then in Make: add any Pidgeon module, Create a connection, and paste it. Make tests it against GET /v1/me and names the connection after the key.

One address and one key per scenario, with the least access it needs. Whatever the key reaches, the scenario can reach, and so can any model you give the scenario's output to.

The scenarioAccess
Only watches, searches and readsread
Saves drafts, hands threads overdraft
Replies or sendssend
Creates addressesadmin

A test key (pgn_test_…) works the same way, and nothing it sends leaves Pidgeon (test mode). Build the scenario on one, deliver mail to it with POST /v1/test/inbound, and switch the connection to a live key when it works. A test key cannot create addresses.

The triggers

Both are instant: a scenario runs when the event happens, not on a schedule. Create a webhook, pick the address, and the app creates a webhook on Pidgeon for that one address. Deleting the webhook in Make deletes it on Pidgeon. Each one counts against your plan's webhook endpoints while it exists.

Pidgeon's webhooks carry IDs and metadata, never a body, so each trigger reads what the delivery names with your connection's key before the scenario starts. What comes out is what Pidgeon holds, never what the delivery said.

Watch messages

GET /v1/messages/{id}, after a message.received delivery. Spam starts it only with Include spam on. The address must keep mail: one without an inbox stores nothing to read. Body chooses the clean text, with anything hidden or addressed to an AI taken out, or the original.

Every field a router or filter branches on is a plain field, so no model is needed to decide where mail goes:

json
{
"event": "message.received",
"event_id": "0044ed22-…",
"message_id": "1a70b25d-…",
"thread_id": "463ec73a-…",
"address": "quotes@yourdomain.com",
"address_id": "9b2f1c3d-…",
"direction": "inbound",
"from": "priya@supplier.de",
"from_name": "Priya Raman",
"to": ["quotes@yourdomain.com"],
"cc": [],
"delivered_to": "quotes@yourdomain.com",
"subject": "Angebot 4471",
"received_at": "2026-10-26T09:14:03Z",
"spam": false,
"firewall_verdict": "clean",
"auth_spf": "pass",
"auth_dkim": "pass",
"auth_dmarc": "pass",
"code": null,
"codes": [],
"links": [],
"text": "Guten Tag, anbei unser Angebot …",
"reply_text": "Guten Tag, anbei unser Angebot …",
"attachments": [],
"test": false
}

firewall_verdict is clean, suspicious or blocked (Firewall). Put a filter on it before a model reads text.

Watch thread hand-offs

GET /v1/threads/{id}, after a thread.updated or thread.assigned delivery. Direction is To a person (somebody takes the thread, or it is assigned to somebody), Back to the agent, or Either. A status change that moves nobody does not start it.

json
{
"event": "thread.updated",
"event_id": "7d8e9f0a-…",
"thread_id": "463ec73a-…",
"address": "quotes@yourdomain.com",
"subject": "Angebot 4471",
"from": "priya@supplier.de",
"status": "open",
"owner_type": "human",
"assignee_id": null,
"message_count": 2,
"previous_owner_type": "agent",
"reason": "Asks for a discount above the limit",
"changed_at": "2026-10-26T09:20:11Z"
}

Use it to tell a channel or a CRM that a person must act, or to stop a scenario that would answer a thread somebody now holds.

The modules

ModuleEndpointWhat it doesAccess
Create an addressPOST /v1/identitiesAn address on your domain or pidgeon.email, and who it may write toadmin
Send a messagePOST /v1/messagesWrites to people outside your account: a new conversationsend
Reply to a messagePOST /v1/messages/{id}/replyWrites to people outside your account: answers in the threadsend
Create a draftPOST /v1/draftsA draft in the address's Drafts, threaded as a replydraft
Update a threadPATCH /v1/threads/{id}Hands it to a person, with a reason and an assignee, or backdraft
Get a messageGET /v1/messages/{id}One message, as the trigger outputs it, clean by defaultread
Search messagesGET /v1/messages/searchMessages by words, address, folder, sender or recipientread
Get a cursorGET /v1/messages/awaitMarks now at an address, for Wait for a messageread
Wait for a messageGET /v1/messages/awaitThe next message to arrive, with its code, or timed_outread
Make an API callanyAny endpoint in the reference, with the key addedany

A draft waits because nothing sends it. Give a scenario that drafts a draft key: it cannot send. A person reads the draft and sends it from the Pidgeon inbox or a mail client.

Waiting for a verification code

Make stops a module after 40 seconds, so Wait for a message waits at most 30 and answers as soon as the mail arrives. Put Get a cursor before the step that causes the mail, and its cursor into Wait for a message's Cursor:

  1. Get a cursor for the address.
  2. The step that makes the service send mail, such as HTTP → Make a request to its sign-up form.
  3. Wait for a message, with the cursor and Only mail with a code. code holds the most likely code.

The cursor means a code that arrives during step 2 is not missed. Without one, a message from the last 2 minutes counts, so narrow it with From. From matches only mail that passed DMARC, because anybody can write a From line.

A slower sender needs a longer wait: put Wait for a message under a Repeater, pass each run's cursor to the next, and stop at the first bundle with timed_out false.

Templates

Each one is a blueprint: save the file, then in a new scenario ⋯ → Import blueprint and choose it. Then pick your connection and address in each module.

  • draft-and-hand-off.blueprint.json — mail that is clean and not spam gets a drafted acknowledgement, threaded as a reply, and the thread is handed to a person with the reason "A draft reply is ready to read and send". The key is draft on one address.
  • wait-for-a-code.blueprint.json — Get a cursor, then Wait for a message. Insert the step that causes the mail between them. The key is read on one address.

Mail delivered with POST /v1/test/inbound has no SPF, DKIM or DMARC result, so auth_dmarc is empty on a test key. A filter on it stops test mail; take it out while you test.

Both are also in pidgeon-ai/pidgeon-examples.

Limits

  • A trigger watches one address. For several, add a scenario for each, with a key for each.
  • Each trigger's webhook uses one webhook endpoint against your plan.
  • Search messages returns at most 100 messages a page, and stops at Limit.
  • Wait for a message waits at most 30 seconds a run.
  • An address that keeps no mail cannot start Watch messages: there is nothing to read.