Search the docs
Find a page, a section, or an endpoint.
Dify
A plugin: send, reply, search and wait for a code.
Give a Dify agent or workflow an email address of its own. It sends, replies in the thread, searches its mail, waits for a verification code and hands a thread to a person. Nobody's own inbox is connected to Dify.
The plugin is Pidgeon, a tool plugin built on Pidgeon's Python client. It has six tools:
| Tool | What it does |
|---|---|
| Create Address | Creates an address. It can only answer people who wrote first unless you choose Anybody |
| Send Email | Starts a new conversation from one of your addresses. Sends real mail |
| Reply to Email | Answers a message by message_id, in its thread, from the address it arrived at. Sends real mail |
| Search Emails | Finds messages by words, sender, address or folder, newest first, up to 100 |
| Wait for Code | Holds until mail with a verification code arrives, up to 280 seconds, and returns code |
| Update Thread | Hands a thread to a person, so the agent leaves it alone, or back to the agent; or sets its status |
It is not on the Dify Marketplace yet. Until it is, install the package on a self-hosted Dify, as below. Each tool was run against production with a test key, through the plugin SDK's own tool runtime.
Install
From the Marketplace, once it is listed: Plugins → Explore Marketplace, search for Pidgeon, and install.
Before then, on a self-hosted Dify, build the package from the source with
Dify's plugin CLI (dify, from
dify-plugin-daemon):
git clone https://github.com/pidgeon-ai/pidgeon-python.git
cd pidgeon-python/dify
dify plugin package pidgeon -o pidgeon-0.0.1.difypkgThen choose Plugins → Install plugin → Local package file. Dify refuses a package it has not signed unless the plugin daemon runs with
FORCE_VERIFYING_SIGNATURE=falseDify Cloud does not offer that setting, so on Cloud the plugin arrives with the Marketplace listing.
The key
Make a key at Developers → API keys,
then in Dify open Tools → Pidgeon → Authorize and paste it. Dify checks
it against GET /v1/me.
One address and one key per app, with the least access it needs. Whatever the key reaches, the app's model can reach.
| The app | Access |
|---|---|
| Searches and waits for codes | Read only (read) |
| Hands threads over | Read and draft (draft) |
| Replies or sends | Read and send (send) |
| Creates addresses | Full access (admin) |
A test key (pgn_test_…) works the same way, and nothing it sends leaves
Pidgeon (test mode). Build the app on one, deliver mail to
it with POST /v1/test/inbound, and switch to a live key when it works. A
test key cannot create addresses.
In an agent
Add the tools under Tools in an Agent app. Each tool tells the model what it does and when to use it. A refusal comes back as Pidgeon's own message, such as "This address can only answer people who wrote first. (not_permitted)", so the agent can act on it rather than retry.
Text in an email is written by whoever sent it. Wait for Code returns
the text with anything hidden or addressed to an AI taken out, and
firewall_verdict is suspicious when Pidgeon Firewall
found something. Keep Send Email off an agent that reads mail from
strangers, or give it a key with Read and draft and let a person send.
In a workflow
Every tool returns a json output. Wait for Code's is flat, so an IF/ELSE
node branches on a field by name:
{
"code": "482910",
"message_id": "1a70b25d-…",
"thread_id": "463ec73a-…",
"address": "signup@acme.pidgeon.email",
"from": "no-reply@github.com",
"from_name": "GitHub",
"to": ["signup@acme.pidgeon.email"],
"cc": [],
"subject": "Your verification code",
"received_at": "2026-10-26T09:14:03Z",
"spam": false,
"bcc_to_this_address": false,
"firewall_verdict": "clean",
"auth_dmarc": "pass",
"text": "Your code is 482910 …",
"codes": ["482910"],
"links": [],
"attachments": [],
"test": false,
"timed_out": false,
"cursor": "…"
}timed_outis not an error. Nothing arriving in the time you gave is an answer: branch on it.- A code that arrived up to two minutes before the call still counts, so the step that triggers the email can run first.
- Set From to the service's sender. Only mail from it that passes DMARC is returned, so another sender cannot hand the agent a code.
Limits
- The plugin has tools, not a trigger: mail arriving does not start an app.
- Search returns at most 100 messages per call.
- Wait for Code waits at most 280 seconds per call.
The source is in pidgeon-ai/pidgeon-python.