# Activepieces

> Give an Activepieces flow its own email address: a trigger when mail arrives that checks every signature, and actions to reply, hand off and wait for a code.

Give an [Activepieces](https://www.activepieces.com) flow an email address of
its own. Mail that arrives starts the flow; the flow replies, hands threads to
people and waits for verification codes. Nobody's own inbox is connected to
Activepieces, and a model in the flow only ever reads the one message in
front of it.

The piece is **Pidgeon**, a community piece. It has one trigger and six
actions:

- **Email Received** starts a flow when mail arrives at an address.
- **Create Address**, **Send Email**, **Reply to Email**, **Update Thread**,
  **Wait for Code** and **Search Emails** act on mail, and **Custom API Call**
  reaches any other [endpoint](https://pidgeon.ai/docs/reference.md).

**It is not in Activepieces' own catalogue yet**, so a platform admin installs
it from npm. Activepieces has paused pull requests from outside its team since
16 July 2026, which is how a piece would get into the catalogue. The piece
was built and tested inside Activepieces' repository and runs its framework's
own HTTP client against production with a test key.

## Install

As the platform admin: **Platform Admin → Catalogue → Pieces → Install
Piece**, choose npm, and enter the package name.

```text
@pidgeon-ai/piece-pidgeon
```

Once it is installed, it appears in the flow builder for every project on the
platform. On Activepieces Cloud, installing a piece is part of a paid plan; on
a self-hosted Activepieces, the admin who set it up is the platform admin.

The trigger needs Activepieces on a public HTTPS URL, because Pidgeon delivers
webhooks only to HTTPS. Activepieces builds its webhook URLs from
`AP_FRONTEND_URL`.

## The connection

Make a key at [Developers → API keys](https://pidgeon.ai/app/developers/keys?utm_source=activepieces),
then in the flow builder: **Pidgeon → Connection → Create connection**, and
paste it. Activepieces checks it against `GET /v1/me`.

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

| The flow                          | Access |
| --------------------------------- | ------ |
| Only triggers, searches and reads | read   |
| Hands threads over                | draft  |
| Replies or sends                  | send   |
| Creates addresses                 | admin  |

A **test key** (`pgn_test_…`) works the same way, and nothing it sends leaves
Pidgeon ([test mode](https://pidgeon.ai/docs/test-mode.md)). Build the flow 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 trigger

**Email Received** watches one address. When the flow is published, it
creates its own webhook on Pidgeon for that address and keeps the signing
secret in the flow's store, where only the trigger reads it. It checks the
signature on every delivery: a request that is not signed by Pidgeon, or is
more than five minutes old, starts nothing. When the flow is turned off, the
webhook is deleted. A published flow uses one of your plan's
[webhook endpoints](https://pidgeon.ai/docs/limits.md).

Pidgeon's webhooks carry ids and metadata, never a body, so the trigger reads
the message and its thread before the flow starts. Every field a Router
branches on is a plain field:

```json
{
"event": "message.received",
"message_id": "1a70b25d-…",
"thread_id": "463ec73a-…",
"address": "quotes@acme.pidgeon.email",
"from": "priya@supplier.de",
"from_name": "Priya Raman",
"to": ["quotes@acme.pidgeon.email"],
"cc": [],
"subject": "Angebot 4471",
"spam": false,
"from_self": false,
"bcc_to_this_address": false,
"is_reply": false,
"firewall_verdict": "clean",
"auth_dmarc": "pass",
"thread_owner_type": "agent",
"text": "Guten Tag, anbei unser Angebot …",
"codes": [],
"links": [],
"attachments": [
  { "id": "0b1d4e8c-…", "filename": "angebot.pdf", "content_type": "application/pdf", "size": 48211 }
],
"test": false
}
```

- **`text` is clean by default.** [Pidgeon Firewall](https://pidgeon.ai/docs/firewall.md) takes
  out anything hidden or addressed to an AI. `firewall_verdict` is
  `suspicious` when it found something: route that mail to a person, not to a
  model. **Original** is the text exactly as sent, one dropdown away.
- **`from_self` is the one to check before replying.** Mail one of your
  addresses sends to another arrives as ordinary mail.
- **`auth_dmarc` is `pass` only when the From is not forged.** Check it
  before acting on who an email says it is from.
- **`thread_owner_type`** is `human` once a person has taken the thread.
  Leave those alone.

**Test** in the trigger reads the newest message at the address, so the data
panel shows real fields. Send the address an email first.

## The actions

| Action          | What it does                                                                                                                    |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Create Address  | Creates an address. `reply_only` by default: it can answer mail but not write to a stranger first                               |
| Send Email      | Starts a new conversation. Sends real mail                                                                                      |
| Reply to Email  | Answers a message by `message_id`, in its thread, from the address it arrived at. Sends real mail                               |
| Update Thread   | Hands a thread to a person, so the agent leaves it alone, or back to the agent; or changes its status                           |
| Wait for Code   | Holds until the next mail with a code arrives at an address, up to 280 seconds. `code` is the code; `timed_out` is not an error |
| Search Emails   | Finds messages by words, sender, address or folder, newest first, up to 100                                                     |
| Custom API Call | Any other endpoint, with the connection's key                                                                                   |

Send Email and Reply to Email send once per step per run: a retry of a failed
step carries the same `Idempotency-Key`, so Pidgeon does not send the mail
twice.

## Read a sign-up code

A flow that signs a service up with its own address:

1. **Wait for Code** on the address, with **From** set to the service's
   sender and **After** set to `now`.
2. In a branch beside it, the step that signs up with the address.
3. The next step reads `{{step_1.code}}`.

Wait for Code holds the step for up to 280 seconds, and Activepieces stops a
run after 600 by default (`AP_FLOW_TIMEOUT_SECONDS`).

## Limits

- The trigger watches one address. For several, add a flow for each, with a
  key for each.
- Each published flow with the trigger uses one webhook endpoint against your
  plan.
- Search returns at most 100 messages per run.
- Wait for a Code waits at most 280 seconds per run.
- The trigger starts flows for `message.received`, and for `message.spam`
  with **Include Spam** on. A thread changing hands does not start a flow
  yet; [`thread.updated`](https://pidgeon.ai/docs/webhooks.md) reaches a Webhook trigger.

The source is in [pidgeon-ai/pidgeon-js](https://github.com/pidgeon-ai/pidgeon-js/tree/main/activepieces/pidgeon).
