Skip to main content

pyxle-mail

Email for Pyxle apps through one mail.service. Write await mail.send(...) once; deliver it over SMTP, Resend, or any provider that implements the MailProvider contract — swap providers by config, not code. With no configuration it logs instead of sending, so local dev works with zero setup.

Install

pip install pyxle-mail                # SMTP + console need nothing else
pip install "pyxle-mail[resend]"      # + httpx, for the Resend provider

Quickstart

Declare the plugin (it depends on nothing, so order doesn't matter):

{ "plugins": ["pyxle-db", "pyxle-auth", "pyxle-mail"] }

Send from any loader, action, or API route:

from pyxle_mail import get_mail_service

@action
async def invite(request):
    await get_mail_service().send(
        to="user@example.com",
        subject="You're invited",
        html="<p>Welcome aboard.</p>",
        text="Welcome aboard.",
    )
    return {"ok": True}

Out of the box that logs the email (console provider) — no account, key, or SMTP server. Configure a provider when you want real delivery.

Providers

Provider When Needs
console Local dev, dry-run — logs a summary, sends nothing. The default. nothing
smtp Any mail server (Gmail, Fastmail, MailHog, a corporate relay). host (+ usually user/password)
resend Resend — modern transactional API. pyxle-mail[resend] + API key

A provider is just an object with a name and async send(message). Implement pyxle_mail.MailProvider to add your own (SendGrid, Mailgun, SES, Postmark, …) and pass it in — that's how the ecosystem grows providers without touching app code.

Settings

Configure in pyxle.config.json (camelCase) or with PYXLE_MAIL_* environment variables. Precedence is config > env > default. Keep secrets (SMTP password, API key) in the environment, not the committed config.

Config key Env variable Default What it does
fromAddress PYXLE_MAIL_FROM — Default sender. Required for any real provider.
fromName PYXLE_MAIL_FROM_NAME — Optional display name.
replyTo PYXLE_MAIL_REPLY_TO — Default Reply-To.
provider PYXLE_MAIL_PROVIDER "console" console | smtp | resend.
dryRun PYXLE_MAIL_DRY_RUN false Force the console provider regardless of provider — the safe switch for staging.
smtpHost PYXLE_MAIL_SMTP_HOST — SMTP server host.
smtpPort PYXLE_MAIL_SMTP_PORT 587 SMTP port.
smtpUsername PYXLE_MAIL_SMTP_USERNAME — SMTP auth user.
smtpPassword PYXLE_MAIL_SMTP_PASSWORD — SMTP auth password (env only).
smtpUseTls PYXLE_MAIL_SMTP_TLS true STARTTLS (port 587).
smtpUseSsl PYXLE_MAIL_SMTP_SSL false Implicit TLS (port 465).
resendApiKey PYXLE_MAIL_RESEND_API_KEY — Resend API key (env only).

A misconfigured real provider (e.g. provider: "resend" with no key, or any real provider with no fromAddress) fails loud at startup rather than on the first send.

Example: Resend in production

PYXLE_MAIL_PROVIDER=resend
PYXLE_MAIL_RESEND_API_KEY=re_...
PYXLE_MAIL_FROM=hello@yourdomain.com
PYXLE_MAIL_FROM_NAME="Your App"

Resend requires a verified sender domain (SPF/DKIM DNS records) before it will deliver from anything but its test address.

Sending mail

mail = get_mail_service()

result = await mail.send(
    to=["a@example.com", "b@example.com"],   # str or list
    subject="Monthly digest",
    html="<h1>Hi</h1>",
    text="Hi",                               # at least one of html/text
    reply_to="support@yourdomain.com",
    headers={"List-Unsubscribe": "<https://you/u?t=…>"},   # one-click unsubscribe
)
result.message_id  # provider's id
result.provider    # "console" | "smtp" | "resend" | …

Bad input raises InvalidMessage (no recipient, no body, bad address); a provider rejection or transport failure raises SendError. Both inherit MailError.

What pyxle-mail is not

  • Not a newsletter/campaign system. It sends transactional and one-off mail. Audiences, scheduling, and analytics are out of scope.
  • Not a template engine. Pass rendered HTML/text — use your own templating (Pyxle components, Jinja, f-strings).
  • Not a queue. A send awaits the provider. For high volume or retries, enqueue with a background-jobs plugin and send from the worker.

See also

Metadata

Release files for pyxle-mail 0.1.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 pyxle-mail 0.1.0
File Size Uploaded
pyxle_mail-0.1.0.tar.gz 18.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyxle-mail 0.1.0
File Interpreter ABI Platform
pyxle_mail-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 36.6 kB

Release files / pyxle_mail-0.1.0.tar.gz

Download URL pyxle_mail-0.1.0.tar.gz
Size 18.6 kB
Tags Source
SHA-256 checksum
How to use checksums
09e68bd5a836220a60172f666aaf213f092e6db3003ca846ee11c1e1e5800014
BLAKE2b-256 checksum
How to use checksums
ba9e70758715d23583cc95b81a63ef97c2d4d34440a43dea85449ec2082cdee1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jun 15, 2026.

Transparency log

Release files / pyxle_mail-0.1.0-py3-none-any.whl

Download URL pyxle_mail-0.1.0-py3-none-any.whl
Size 18.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
45b13b115b183942fcbb5a94c85d5e3c02b382a3848ee25f0da5ac024984f718
BLAKE2b-256 checksum
How to use checksums
a06bcc5893b91e7c6301c56f67c10600da8cac42ab181803386df5db80e92ebd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 Jun 15, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.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