Skip to main content

xlambda-email

Send email from your own domain, and — the reason this package exists — turn on receiving with almost no glue code.

pip install xlambda-email

Python port of @xlambda-tech/email; same API, same webhook signature scheme.

Receiving inbound mail

from xlambda.email import create_inbound_handler
from xlambda.core.webhooks.adapters.flask import create_flask_blueprint

handler = create_inbound_handler(
    secret=os.environ["XLAMBDA_WEBHOOK_SECRET"],
    on_received=lambda msg: InboundEmail.objects.create(
        sender=msg["from"], subject=msg["subject"], html=msg["htmlBody"]
    ),
)

app.register_blueprint(create_flask_blueprint(handler), url_prefix="/webhooks/email")

That's the whole integration. Point your project's webhookUrl (dashboard → Settings → Webhooks) at this endpoint and inbound mail on your managed domain starts landing in your own database.

This is the only place a message's body ever exists. The platform stores no message content server-side by design — only metadata (from, to, subject, size) shows up in client.domains.messages(). If your handler doesn't persist what it needs on receipt, it's gone. Deliveries do retry 3× with exponential backoff if your endpoint errors, so a transient failure isn't fatal — an on_received that never runs at all is.

On FastAPI, use the async handler so your callback doesn't block the loop:

from xlambda.email import create_async_inbound_handler
from xlambda.core.webhooks.adapters.fastapi import create_fastapi_router

handler = create_async_inbound_handler(secret=..., on_received=save_to_db)
app.include_router(create_fastapi_router(handler), prefix="/webhooks/email")

No framework at all:

handler.to_wsgi_app()   # sync handler
handler.to_asgi_app()   # async handler
result = handler.handle(raw_bytes, signature_header)   # verify it yourself

on_sent / on_delivered / on_bounced are also available if you want one endpoint handling the outbound-side events too. Anything that isn't an email.* event is acknowledged with a 200 and ignored, so pointing a shared webhook URL here is safe.

Sending

from xlambda.email import EmailClient

client = EmailClient(api_key=os.environ["XLAMBDA_API_KEY"])

sent = client.emails.send(
    from_="you@yourdomain.com",
    to="them@example.com",          # or ["a@x.com", "b@y.com"]
    subject="Hello",
    html="<p>Hello</p>",
)
print(sent["id"])

from_ carries the trailing underscore because from is a Python keyword; it goes out on the wire as from, and comes back as from in responses. Everything else matches Resend's argument names (to/cc/bcc/reply_to/ subject/text/html), so switching from Resend — or back — is close to a find-and-replace on the import.

Async:

from xlambda.email import AsyncEmailClient

async with AsyncEmailClient(api_key=...) as client:
    await client.emails.send(from_=..., to=..., subject=..., html=...)

Domains

Sending from a domain requires registering it and publishing the DNS records that come back:

domain = client.domains.create(project_id="proj_123", domain="notify.example.com")

for record in domain["dkimRecords"]:
    print(record["name"], record["content"])
print(domain["mxTarget"], domain["spfRecord"], domain["dmarcRecord"])

client.domains.verify(domain["id"])     # re-checks MX/SPF/DKIM/DMARC

Message metadata (bodies are never stored — see above):

page = client.domains.messages(domain["id"], direction="received", limit=50)
page["nextCursor"]      # pass back as cursor= for the next page

SMTP credentials, for systems that speak SMTP rather than HTTP:

users = client.domains.sending_users(domain["id"])
user = users.create()   # user["password"] is shown here and never again

Errors

from xlambda.email import XlambdaError

try:
    client.emails.send(from_=..., to=..., subject=..., html=...)
except XlambdaError as err:
    if err.status == 429:
        ...     # rate limited, or the platform's outbound warm-up cap
    print(err.code, err.message, err.details)

XlambdaError and the webhook types are re-exported here, so you never need to depend on xlambda-core directly.

Conventions

Arguments are snake_case; responses are the API's own camelCase, returned as plain dicts typed by TypedDict. See xlambda-core for why.

Metadata

Release files for xlambda-email 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 xlambda-email 0.2.0
File Size Uploaded Provenance Actions
xlambda_email-0.2.0.tar.gz 10.2 kB With provenance Details for xlambda_email-0.2.0.tar.gz

Built distribution (wheel)

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

Total release size: 21.1 kB

Release files / xlambda_email-0.2.0.tar.gz

Download URL xlambda_email-0.2.0.tar.gz
Size 10.2 kB
Tags Source
SHA-256 checksum
How to use checksums
eed4dcb96a4e38f8dd64c0a752997fc52daec417115d7ac8bf8ec537671baae6
BLAKE2b-256 checksum
How to use checksums
f3f4885070a3f02612949f3db0d13bea2a395644de7896f52a3263fb2989e6eb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 6, 2026.

Transparency log

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

Download URL xlambda_email-0.2.0-py3-none-any.whl
Size 10.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9eec6fba03b7b9ba7a0e90454955565e84df32a82b7be01ad9f9dcad36f6d1c7
BLAKE2b-256 checksum
How to use checksums
725f3f52f2527ab8c1ff4d1f33d66dc5addc7fd798a925b0de83ae6ea4ea6878
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 6, 2026.

Transparency log

Security for xlambda-email 0.2.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

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for xlambda_email-0.2.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 Sep 6, 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.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

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