Skip to main content

UniOne FastAPI

FastAPI License: MIT

FastAPI integration of the UniOne Python SDK (``): the API client in the dependencies (Depends(get_client)), a webhook endpoint with signature check.

API reference · Docs for LLMs: llms.txt

Install

pip install unione-fastapi

Requirements: Python 3.8+, FastAPI 0.100+.

Configuration

Get an API key in the control panel and keep it in the environment, never in code:

export UNIONE_API_KEY="your-api-key"

Settings are read from the environment by default:

UNIONE_API_KEY=YOUR_API_KEY
UNIONE_HOST=us1.unione.io

us1.unione.io in the example is for an account on another datacenter; without the host setting the package uses api.unione.io.

Option Environment variable Default Description
Settings.api_key UNIONE_API_KEY User or project API key (required).
Settings.host UNIONE_HOST api.unione.io API host (datacenter: api.unione.io, eu1.unione.io, us1.unione.io) or a full base URL.
Settings.timeout UNIONE_TIMEOUT 30 Request timeout, seconds.
Settings.webhook_key UNIONE_WEBHOOK_KEY API key the webhooks are signed with (the key of the user/project of the webhook), if it differs from the API key.
Settings.webhook_path UNIONE_WEBHOOK_PATH /unione/webhook Path of the webhook endpoint.

Other settings in code: app.dependency_overrides[get_settings] = lambda: Settings(api_key=...).

Webhooks

The endpoint answers GET with 200 OK (the URL check of webhook/set) and accepts the POST notifications: it checks the auth field (MD5 of the request body in which the auth value is replaced by the API key), answers 401 to a wrong signature, and passes the notification and each of its events to the application.

from unione_wrapper import Webhooks

webhooks = Webhooks()


@webhooks.on_event
async def on_event(event, user):
    data = event.event_data
    print(event.event_name, data.email, data.status)


app.include_router(webhooks.router())  # /unione/webhook

Register the URL of the endpoint with webhook/set, e.g. https://example.com/unione/webhook.

Answer 200 within 3 seconds and process the events in the background (a queue): otherwise UniOne repeats the request every 10 minutes for 24 hours (retry_count grows), and a handler that failed every request for at least 10 events of a day is turned inactive. The handler must take at least 5 parallel requests of up to 100 events each; deduplicate, a repeat delivers the same events again.

API client

Every section of the API is available on the client, see the SDK docs.

from fastapi import Depends, FastAPI
from unione import UniOneClient
from unione_wrapper import get_client

app = FastAPI()


@app.get("/usage")
def usage(client: UniOneClient = Depends(get_client)):  # the client is synchronous: a def endpoint
    info = client.system.info()
    return {"emails_sent": info.accounting.emails_sent if info.accounting else None}

Sending through the API

The client calls email/send with all of its options: templates, substitutions, metadata, tracking. Fields of the request and other examples: Python SDK docs.

from fastapi import Depends
from unione import UniOneClient
from unione.api.email import EmailSendRequest, MessageObject, MessageObjectRecipientsItem as Recipient
from unione.common import BodyObject
from unione_wrapper import get_client


@app.post("/send")
def send_email(client: UniOneClient = Depends(get_client)):
    response = client.email.send(EmailSendRequest(
        message=MessageObject(
            recipients=[Recipient.from_address("Alex <alex@example.com>")],
            from_email="shop@example.com",
            from_name="Shop",
            subject="Your order",
            body=BodyObject.from_html("<p>Hello, {{to_name}}! Your order has shipped.</p>"),
        ),
    ))
    return {"job_id": response.job_id}

No domain yet: the test sandbox domain

Until your domain is verified, send from the sandbox domain: a test domain every account has, already verified, with DKIM and SPF set. It delivers only to addresses you confirm (the confirmation link comes by email, valid 48 hours), up to 100 emails a day. The sandbox domain is deleted 31 days after it was created, but you can get it again: the next domain/get-sandbox-domain call creates a new one (with a new name, so read it from the call, do not hardcode it).

The examples use the API client of the package (see the configuration above).

from fastapi import Depends
from unione import UniOneClient
from unione.api.confirmed_email import ConfirmedEmailRequest
from unione.api.email import EmailSendRequest, MessageObject, MessageObjectRecipientsItem as Recipient
from unione.common import BodyObject
from unione_wrapper import get_client


@app.post("/sandbox")
def send_from_sandbox(client: UniOneClient = Depends(get_client)):
    # 1. Your sandbox domain: created on the first call, the same one afterwards
    domain = client.domain.get_sandbox_domain().domain

    # 2. Confirm your inbox: UniOne sends a link, click it within 48 hours
    client.confirmed_email.create(ConfirmedEmailRequest(email="you@example.com"))

    # 3. Send from any address on the sandbox domain to the confirmed one
    client.email.send(EmailSendRequest(
        message=MessageObject(
            recipients=[Recipient.from_address("you@example.com")],
            from_email=f"noreply@{domain}",
            subject="Hello from UniOne",
            body=BodyObject.from_html("<p>It works!</p>"),
        ),
    ))

    return {"domain": domain}
Sandbox limits
  • Sends only to confirmed addresses: up to 10 per account.
  • Up to 100 emails a day.
  • The sandbox domain expires after 31 days; call domain/get-sandbox-domain again to get a new one.
  • Works only with the account API key, not a project key.
  • Not listed in domain/list; the other domain/* methods do not apply to it.

The sandbox is for development and testing. Do not use a sandbox domain as the sender in production.

The same with curl
BASE=https://api.unione.io/en/transactional/api/v1

curl -s -X POST $BASE/domain/get-sandbox-domain.json \
  -H "Content-Type: application/json" -H "X-API-KEY: $UNIONE_API_KEY" -d '{}'

curl -s -X POST $BASE/confirmed-email/create.json \
  -H "Content-Type: application/json" -H "X-API-KEY: $UNIONE_API_KEY" \
  -d '{"email": "you@example.com"}'

curl -s -X POST $BASE/email/send.json \
  -H "Content-Type: application/json" -H "X-API-KEY: $UNIONE_API_KEY" \
  -d '{
        "message": {
          "recipients": [
            {
              "email": "you@example.com"
            }
          ],
          "from_email": "noreply@<sandbox-domain>",
          "subject": "Hello from UniOne",
          "body": {
            "html": "<p>It works!</p>"
          }
        }
      }'

Going to production

1. Verify your sending domain. domain/get-dns-records returns the verification record, the DKIM key and the DMARC target; add the records at your DNS provider, then domain/validate-verification-record and domain/validate-dkim.

Type Host Value Where it comes from
TXT @ unione-validate-hash=… verification-record of the response, as is
TXT <selector>._domainkey (the selector is on the domain page of the control panel, e.g. us) v=DKIM1; k=rsa; p=<dkim> dkim of the response after v=DKIM1; k=rsa; p=
TXT @ v=spf1 include:spf.unione.io ~all not in the response; add the include to your SPF record if you have one
CNAME _dmarc <domain>.dmarc.… dmarc of the response (UniOne publishes the DMARC policy); keep your own DMARC TXT record instead if the domain has one

2. Make sending reliable.

Retries 429 and 5xx with backoff and the same idempotence_key; fix other errors instead of retrying
Limits 500 recipients and 10 MB per request, attachments included: base64 makes a file about a third larger
Body always html or plaintext (a template can supply it)
Delivery status a webhook, not polling
Queue send from a background job, not inside the web request
Config from_email from config, so the sandbox → production switch needs no code change
Defaults keep the unsubscribe footer and tracking; changing them needs UniOne support

Help

Metadata

Release files for unione-fastapi 1.2.4

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

Source distribution (sdist)

Source distribution for unione-fastapi 1.2.4
File Size Uploaded Provenance Actions
unione_fastapi-1.2.4.tar.gz 8.8 kB With provenance Details for unione_fastapi-1.2.4.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for unione-fastapi 1.2.4
File Interpreter ABI Platform Actions
unione_fastapi-1.2.4-py3-none-any.whl Python 3 none any Details for unione_fastapi-1.2.4-py3-none-any.whl

Total release size: 19.1 kB

Release files / unione_fastapi-1.2.4.tar.gz

Download URL unione_fastapi-1.2.4.tar.gz
Size 8.8 kB
Tags Source
SHA-256 checksum
How to use checksums
71985da5495cf21330c3f5622540b85e13cc14bbebfecd8176252653bbb5cf93
BLAKE2b-256 checksum
How to use checksums
e281eff6abb34a29f5f1aa76d9406d5d8a9c579249ccc62118193db6459aee50
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 21, 2026.

Transparency log

Release files / unione_fastapi-1.2.4-py3-none-any.whl

Download URL unione_fastapi-1.2.4-py3-none-any.whl
Size 10.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2364e923bde20b0afa326b10565b2a52473b93ea0956de4c9d9a3db0fcd55fa0
BLAKE2b-256 checksum
How to use checksums
ba9b5f13405cff7f8bdddd8b580cc800a1d1387efd7817611814575e719d3245
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 21, 2026.

Transparency log

Security for unione-fastapi 1.2.4

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 unione_fastapi-1.2.4.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 21, 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.

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