Skip to main content

Faivelo Python SDK

The official Faivelo SDK for Python. Give your code, or your AI agent, a real email inbox on your own domain: send and receive, drafts a person can review, labels, threads, attachments, domains and DNS, and signed webhooks.

Every inbox is a full mailbox, so a person can open the same inbox in Faivelo webmail (or any IMAP client) and see exactly what the agent sees.

Install

pip install faivelo

Requires Python 3.9 or newer. Fully typed; the only dependency is httpx.

Setup

Create an API key in your Faivelo dashboard under Settings → Developers, then:

from faivelo import Faivelo

faivelo = Faivelo("fvl_live_xxxxxxxx")  # or set FAIVELO_API_KEY and call Faivelo()

For asyncio, AsyncFaivelo has the same methods:

from faivelo import AsyncFaivelo

async with AsyncFaivelo() as faivelo:
    usage = await faivelo.usage.get()

Each key carries scopes (mail:read, mail:send, mailboxes:write, ...). A call fails with a 403 if the key lacks the scope it needs; the required scope is noted in every method's docstring.

Responses are plain dicts with the API's own keys (message["messageId"]). faivelo.types describes them for your editor and type checker.

Give an agent an inbox

mailbox = faivelo.mailboxes.create(domain="acme.com", local_part="agent", display_name="Acme Agent")
print(mailbox["fullAddress"])              # agent@acme.com
print(mailbox["credentials"]["password"])  # shown once: also works over IMAP/SMTP

Send

sent = faivelo.mailboxes.send(
    "agent@acme.com",
    to="user@example.com",
    subject="Your order has shipped",
    text="Tracking number 1Z999...",
    idempotency_key="order-4812-shipped",
)

The message lands in the mailbox's Sent folder. idempotency_key makes a retry safe: a repeat with the same key within 24 hours returns the first result with replayed: True instead of sending again, which matters when an agent loop retries a step.

Attachments take bytes and are encoded for you:

faivelo.mailboxes.send(
    "agent@acme.com",
    to="user@example.com",
    subject="Invoice",
    text="Attached.",
    attachments=[{"filename": "invoice.pdf", "content": open("invoice.pdf", "rb").read(), "content_type": "application/pdf"}],
)

Read and reply

inbox = faivelo.messages.list("agent@acme.com", limit=20)
for summary in inbox["messages"]:
    if summary["seen"]:
        continue
    message = faivelo.messages.get("agent@acme.com", summary["uid"])
    faivelo.mailboxes.send(
        "agent@acme.com",
        to=message["from"],
        subject="Re: " + message["subject"],
        text="Thanks, we are on it.",
        in_reply_to=message.get("messageId"),
    )

The whole conversation a message belongs to, the mailbox's own replies included:

thread = faivelo.messages.thread("agent@acme.com", uid)
for item in thread["messages"]:  # oldest first
    print(item["date"], item["from"], item["subject"])

Search, and download attachments:

results = faivelo.messages.search("agent@acme.com", from_="billing@supplier.com", date_after="2026-09-01")
pdf = faivelo.messages.download_attachment("agent@acme.com", uid, "invoice.pdf")
open("invoice.pdf", "wb").write(pdf["content"])

Drafts: let a person approve before it sends

A draft is a real message in the mailbox's Drafts folder. Your agent writes it, a person opens it in webmail and edits or sends it, or your code sends it once it has the go-ahead.

draft = faivelo.drafts.create(
    "agent@acme.com",
    to="customer@example.com",
    subject="Refund approved",
    text="We have refunded your order in full.",
)

current = faivelo.drafts.get("agent@acme.com", draft["uid"])  # includes edits a person made
faivelo.drafts.send("agent@acme.com", draft["uid"])

drafts.update replaces a draft's content and returns it under a new uid. drafts.list and drafts.delete do what they say.

Send later

Add send_at to schedule a send. The message waits in the mailbox's Drafts folder until its time, where a person can read it; deleting it there cancels the send.

from datetime import datetime, timedelta, timezone

result = faivelo.mailboxes.send(
    "agent@acme.com",
    to="customer@example.com",
    subject="Following up",
    text="Checking in on your order.",
    send_at=datetime.now(timezone.utc) + timedelta(days=1),  # or "2026-10-05T09:00:00-04:00"
)
scheduled = result["scheduled"]

faivelo.drafts.send("agent@acme.com", uid, send_at="2026-10-05T09:00:00Z")  # schedule an existing draft

waiting = faivelo.scheduled.list("agent@acme.com")["scheduled"]
faivelo.scheduled.cancel("agent@acme.com", scheduled["id"])  # the draft stays in Drafts

A datetime must carry a timezone. The send goes through the same checks at its time as a send made then, with the API key that scheduled it. scheduled.list also shows the last week's finished sends: sent, cancelled, or failed with a reason.

Allow and block lists

Guardrails for software using a mailbox. An entry is an address or a domain (which covers its subdomains); a block entry always wins, and once an allow list has any entry, only what it names gets through.

faivelo.lists.replace("agent@acme.com", {
    "send": {"allow": ["acme.com", "customer.com"]},  # the API and MCP may only send here
    "receive": {"block": ["spammer.com"]},            # filed in Junk, never the inbox
})

faivelo.lists.add("agent@acme.com", {"receive": {"allow": ["newcustomer.com"]}})
faivelo.lists.remove("agent@acme.com", {"send": {"allow": ["customer.com"]}})
lists = faivelo.lists.get("agent@acme.com")

Send lists are checked on every API and MCP send (a refused recipient is a 403; nothing is sent). Receive lists are enforced by the mail server at delivery. Changing lists needs an account key with mailboxes:write: an agent key can read its lists but not loosen them.

Labels: keep state on the message

Labels are free-form lowercase tags. Use them to track where each message is in your agent's workflow, with no database of your own.

faivelo.messages.label("agent@acme.com", uid, add=["needs-reply"])
todo = faivelo.messages.list("agent@acme.com", label="needs-reply")
faivelo.messages.label("agent@acme.com", uid, add=["answered"], remove=["needs-reply"])

Transactional email

Send from any address on one of your verified domains, no mailbox required:

email = faivelo.emails.send(
    from_="Acme <hello@acme.com>",
    to="user@example.com",
    subject="hello world",
    html="<p>it works!</p>",
)
status = faivelo.emails.get(email["id"])  # delivery status and events

Templates designed in the dashboard are sent by alias: template_alias="receipt", variables={"name": "Ada"}.

Usage and quotas

usage = faivelo.usage.get()
print(usage["plan"], usage["sends"]["remaining"], "sends left until", usage["resetsAt"])

Webhooks

Faivelo signs every webhook. Verify the raw request body before trusting it:

from faivelo import FaiveloWebhookError, verify_webhook

@app.post("/webhooks/faivelo")
async def faivelo_webhook(request):
    try:
        event = verify_webhook(await request.body(), request.headers.get("x-faivelo-signature"), WEBHOOK_SECRET)
    except FaiveloWebhookError:
        return Response(status_code=400)
    if event["type"] == "message.received":
        ...
    return Response(status_code=200)

Pass the body exactly as received (bytes or str), not parsed JSON. Signatures older than five minutes are refused; change that with tolerance_seconds.

Everything else

Namespace What it does
mailboxes list, create, get, update, delete, reset_password, send, list_folders
messages list, get, search, thread, label, flag, move, mark_spam, delete, download_attachment
drafts list, create, get, update, send, delete
scheduled list, cancel
lists get, replace, add, remove
emails send, get
aliases list, create, delete
domains list, get
dns list_records, create_record, update_record, delete_record
drive list, get_download_url
meet create_room
usage get
partner customers, domains, mailboxes, api_keys (for approved resale partners)

Errors

Any non-2xx response raises FaiveloError with the API's message and the HTTP status:

from faivelo import FaiveloError

try:
    faivelo.mailboxes.send("agent@acme.com", to="user@example.com", subject="hi", text="hello")
except FaiveloError as error:
    print(error.status_code, error)  # 0 means the request never reached Faivelo

Options

Faivelo(
    api_key,
    base_url="https://faivelo.com/api/v1",
    timeout=30.0,                # seconds
    http_client=httpx.Client(),  # your own client, for proxies or retries
)

Developing

The async client (src/faivelo/_async_client.py) is the one edited by hand. The sync client is generated from it:

python scripts/gen_sync.py
python -m unittest discover -s tests

License

MIT

Metadata

Release files for faivelo 0.2.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 faivelo 0.2.0
File Size Uploaded
faivelo-0.2.0.tar.gz 31.6 kB Details

Built distribution (wheel)

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

Total release size: 61.9 kB

Release files / faivelo-0.2.0.tar.gz

Download URL faivelo-0.2.0.tar.gz
Size 31.6 kB
Tags Source
SHA-256 checksum
How to use checksums
59d8febbf9e003e6c0103699489239579cf6e626bc7b3046f3bb8a18796d6323
BLAKE2b-256 checksum
How to use checksums
8bbc8365837395287d76398d4b9e55c4dc9ed6dad81ec568fbd9104b5cedf326
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.3

Release files / faivelo-0.2.0-py3-none-any.whl

Download URL faivelo-0.2.0-py3-none-any.whl
Size 30.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ff0c2e36a211600c97b84e2e503bf7860f302db424975613ce1748ef9a2d4750
BLAKE2b-256 checksum
How to use checksums
67eb39a58ebcc2a72b3c68742a9cd79f8da6416419a91950eb3f028e321af25c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.3

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

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