Skip to main content

pidgeon

M8ven Score

Pidgeon is the email inbox API for AI agents. It gives agents their own inboxes so they can work with emails just like a human. This is its Python client.

pip install pidgeon

Create an address, send, receive, reply

import os

from pidgeon import AuthenticatedClient
from pidgeon.api.identities import identities_create
from pidgeon.api.messages import messages_await, messages_reply, messages_send
from pidgeon.models import (
    IdentitiesCreateBody,
    IdentitiesCreateBodyType,
    MessagesReplyBody,
    MessagesSendBody,
)

client = AuthenticatedClient(
    base_url="https://pidgeon.ai/api/v1",
    token=os.environ["PIDGEON_API_KEY"],
)

# 1. An address of its own. Any free name on pidgeon.email works with no DNS.
created = identities_create.sync(
    client=client,
    body=IdentitiesCreateBody(address="scout@pidgeon.email", type_=IdentitiesCreateBodyType.AGENT),
)
address = created.data

# 2. Send from it. A refusal raises PidgeonError; it never comes back as a value.
messages_send.sync(
    client=client,
    body=MessagesSendBody(
        from_=address.email,
        to="ops@example.com",
        subject="Staging access",
        text="Could you send me the access code?",
    ),
)

# 3. Receive: one held-open request until the answer arrives, up to 60 seconds.
awaited = messages_await.sync(client=client, identity_id=address.id, timeout=60)

# 4. Reply into the same conversation. Recipients and subject come off the original.
if awaited.data.message:
    print(awaited.data.message.extracted.codes)
    messages_reply.sync(
        awaited.data.message.id,
        client=client,
        body=MessagesReplyBody(text="Thanks, that worked."),
    )

PIDGEON_API_KEY is a pgn_live_… key. Make one at pidgeon.ai/app/developers/keys, or let your agent sign itself up: pidgeon.ai/skill.md is two calls, and the only thing it needs from a person is a six-digit code.

In production, take a signed webhook rather than holding a request open. Documentation: pidgeon.ai/docs · OpenAPI.

Generated from openapi.json, the same document the TypeScript client is generated from, so an operation has the same name in both: messages_send here is messagesSend there. Every call takes the client; nothing is global.

Errors

A failure raises PidgeonError. Every call returns the success model or raises, so a refused send cannot pass for a sent one. code is the API's own and is what to branch on, message is written for a person and safe to show one, field names the input at fault, and retry_after is in seconds when a limit says when to come back:

from pidgeon.errors import PidgeonError

try:
    messages_send.sync(client=client, body=body)
except PidgeonError as error:
    if error.code == "abuse_blocked":
        ...  # nothing was sent; error.retry_after says when the allowance frees up
    raise

Since 1.19.0. Before it, a call returned Error for a failure the OpenAPI document declared and None for one it did not, and a 403 on a send was one it did not: code that checked isinstance(result, Error) now sees the exception instead. raise_on_unexpected_status no longer changes anything; a status the client cannot read always raises.

from_ and type_ carry a trailing underscore, because from and type are reserved in Python. The wire names are unchanged.

Every operation also has asyncio, and sync_detailed / asyncio_detailed variants that return the whole Response with its status and headers.

Receiving

Mail arriving at an address your code owns becomes an event, and your endpoint is called with a signed payload. Verify before you parse — an unverified payload is an unauthenticated POST that claims a message arrived.

from pidgeon.webhooks import verify_webhook

event = verify_webhook(
    raw_body=request.body,  # the exact bytes, before any json.loads
    headers=request.headers,
    secret=os.environ["PIDGEON_WEBHOOK_SECRET"],
)

raw_body must be the bytes we sent. Parse the JSON after verifying — re-serialising changes key order and the signature will not match. verify_webhook raises rather than returning a boolean, rejects a timestamp outside five minutes before it looks at the signature, and compares in constant time.

The event carries ids, never a body. Exchange one for the content with messages_get.

Creating an address

from pidgeon.api.identities import identities_create
from pidgeon.models import (
    IdentitiesCreateBody,
    IdentitiesCreateBodySendPolicy,
    IdentitiesCreateBodyType,
)

identities_create.sync(
    client=client,
    body=IdentitiesCreateBody(
        address="receipts@yourdomain.com",
        type_=IdentitiesCreateBodyType.AGENT,
        send_policy=IdentitiesCreateBodySendPolicy.REPLY_ONLY,
    ),
)

Say where with address, domain or domain_id — one of the three. Every address you already have carries its domain_id, which is what to use when you are on the shared domain and own nothing to list.

send_policy=IdentitiesCreateBodySendPolicy.REPLY_ONLY makes an address structurally unable to write to anybody who has not written to it first, which is the right default for software.

Pagination

A list returns a page, not an iterator — deliberately. An iterator that hides pagination also hides the rate limit pagination is subject to.

from pidgeon.api.events import events_list

cursor = None
while True:
    page = events_list.sync(client=client, cursor=cursor)
    for event in page.data:
        handle(event)

    cursor = page.next_cursor
    if cursor is None:
        break

What this deliberately does not do

No retry policy, no queue, no caching, no auto-paginating iterator. Those are decisions that belong to your application — an SDK that makes them for you is one you eventually work around.

Versioning

Versioned independently of the API. The API is /v1; this package is 1.x, and semver applies to its own surface.

Full reference: pidgeon.ai/docs.

Contributing

This client is generated from openapi.json, the contract the API validates against, and published from pidgeon-ai/pidgeon-python. A pull request against generated code cannot be merged as written — the fix belongs in the schema. Open an issue and say what is wrong, or email support@pidgeon.ai.

MIT licensed.

Metadata

Release files for pidgeon 1.19.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pidgeon 1.19.0
File Size Uploaded Provenance
pidgeon-1.19.0.tar.gz 158.9 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pidgeon 1.19.0
File Interpreter ABI Platform
pidgeon-1.19.0-py3-none-any.whl Python 3 none any Details

Total release size: 597.9 kB

Release files / pidgeon-1.19.0.tar.gz

Download URL pidgeon-1.19.0.tar.gz
Size 158.9 kB
Tags Source
SHA-256 checksum
How to use checksums
00adc9451819c598c732dbdf914b9862fc5031fc33c446bf4d60d3061f7f0239
BLAKE2b-256 checksum
How to use checksums
97ebe2cea7dcdea8d44dc4eafcc6165e27eb97305406cadb2080fbd6daf7cf9a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Release files / pidgeon-1.19.0-py3-none-any.whl

Download URL pidgeon-1.19.0-py3-none-any.whl
Size 439.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6e986ad26ae62d9f421c2b773b9d8a94cd816d70074da80c316dbad53bf44a58
BLAKE2b-256 checksum
How to use checksums
4f0a496c05aad2b8e6cae05aa19347f49f54beaf4420fa2f84a98b7e0afaaa47
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

Security for pidgeon 1.19.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pidgeon-1.19.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page