Skip to main content

UniOne Django 1.1.3

Web API 1.90

Quickstart guide: Send emails with Python · Docs for LLMs: llms.txt · API reference

Django integration of the UniOne Python SDK (``): the API client in the settings (get_client()), a webhook endpoint with signature check, a Django email transport that sends through `email/send`.

Install

pip install unione-django

Requirements: Python 3.8+, Django 4.2+.

Get an API key

  1. Sign up at cp.unione.io.
  2. Copy your API key from Settings → Security → API.
  3. Put it in an environment variable:
export UNIONE_API_KEY="your-api-key"

The package reads it from UNIONE_API_KEY (see the configuration below).

Server-side only. Use this package in backend code: API routes, server functions, workers. Never ship the API key to a browser or a mobile app, and never commit it.

Configuration

Settings are read from the environment by default:

# settings.py
INSTALLED_APPS = [..., "unione_wrapper"]

UNIONE = {
    "API_KEY": os.environ["UNIONE_API_KEY"],
    "HOST": "us1.unione.io",  # optional
}
MAILERS = {"default": {"BACKEND": "unione_wrapper.mail.EmailBackend"}}  # Django 6.1+
# EMAIL_BACKEND = "unione_wrapper.mail.EmailBackend"                    # Django < 6.1
DEFAULT_FROM_EMAIL = "Shop <shop@example.com>"

# urls.py
urlpatterns = [..., path("unione/", include("unione_wrapper.urls"))]  # /unione/webhook
Option Environment variable Default Description
UNIONE["API_KEY"] UNIONE_API_KEY User or project API key (required).
UNIONE["HOST"] UNIONE_HOST api.unione.io API host (datacenter: api.unione.io, eu1.unione.io, us1.unione.io) or a full base URL.
UNIONE["TIMEOUT"] UNIONE_TIMEOUT 30 Request timeout, seconds.
UNIONE["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 of the UNIONE dict win over the environment variables. The webhook URL is set by include("unione_wrapper.urls"); the view is csrf_exempt.

Send your first email — no DNS setup

Every account can send test email right away from a sandbox domain: pre-verified, DKIM and SPF already set. It delivers only to addresses you confirm.

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

1. Get your sandbox domain (domain/get-sandbox-domain). The call creates the domain on first use and returns the same one afterwards.

from unione_wrapper import get_client

response = get_client().domain.get_sandbox_domain()
print(response.domain)

2. Confirm your inbox (confirmed-email/create). UniOne sends a confirmation email; click the link in it within 48 hours.

from unione_wrapper import get_client
from unione.api.confirmed_email import ConfirmedEmailRequest

response = get_client().confirmed_email.create(ConfirmedEmailRequest(
    email="you@example.com",
))
print(response.status)

Check the status with confirmed-email/list — your address should show "status": "confirmed".

3. Send the email (email/send) from any address on the sandbox domain to the confirmed address.

from unione_wrapper import get_client
from unione.api.email import EmailSendRequest, MessageObject, MessageObjectRecipientsItem
from unione.common import BodyObject

response = get_client().email.send(EmailSendRequest(
    message=MessageObject(
        recipients=[
            MessageObjectRecipientsItem(
                email="you@example.com",
            ),
        ],
        body=BodyObject(
            html="<p>It works!</p>",
        ),
        subject="Hello from UniOne",
        from_email="noreply@<sandbox-domain>",
    ),
))
print(response.job_id)

What you get back

{
  "status": "success",
  "job_id": "1ZymBc-00041N-9X",
  "emails": [
    "you@example.com"
  ]
}

On error the API returns status: "error", a message and a numeric code:

{
  "status": "error",
  "code": 102,
  "message": "Error ID:83274F98-C7D5-11F1-B221-6214FF8707B1. Can not decode key wrongkey"
}

Same flow 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>"
          }
        }
      }'

Sending mail

Messages of the Django email go through email/send: from, to/cc/bcc, reply-to, subject, HTML and text parts, attachments, inline images and X-* headers.

from django.core.mail import EmailMultiAlternatives, send_mail

send_mail("Order shipped", "Your order is on its way", None, ["user@example.com"])

message = EmailMultiAlternatives("Order shipped", "Text", to=["User <user@example.com>"], headers={"X-Order": "42"})
message.attach_alternative("<b>HTML</b>", "text/html")
message.tags = ["orders"]                 # -> tags
message.metadata = {"order_id": "42"}     # -> global_metadata
message.send()
print(message.job_id)

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 django.dispatch import receiver
from unione_wrapper.signals import webhook_event_received


@receiver(webhook_event_received)
def on_event(sender, event, user, **kwargs):
    data = event.event_data
    print(event.event_name, data.email, data.status)

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

API client

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

from unione_wrapper import get_client

info = get_client().system.info()
print(info.accounting)

Sending through the API

Besides the Django email, mail can be sent through the API client directly. 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 unione_wrapper import get_client
from unione.api.email import EmailSendRequest, MessageObject, MessageObjectRecipientsItem
from unione.common import BodyObject

response = get_client().email.send(EmailSendRequest(
    message=MessageObject(
        recipients=[
            MessageObjectRecipientsItem(
                email="user@example.com",
                substitutions={
                    "CustomerId": "12452",
                    "to_name": "John Smith",
                },
            ),
        ],
        body=BodyObject(
            html="<b>Hello, {{to_name}}</b>",
            plaintext="Hello, {{to_name}}",
        ),
        subject="UniOne test email ",
        from_email="email@example.com",
        from_name="John Smith",
    ),
))
print(response.job_id)

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.

Before production: verify your domain

  1. domain/get-dns-records — returns the values for your DNS records.

  2. Add these records at your DNS provider (the domain page of the control panel shows them too):

    Type Host Value
    TXT @ the verification-record value (unione-validate-hash=…)
    TXT <selector>._domainkey (the selector is on the domain page of the control panel, e.g. us) v=DKIM1; k=rsa; p=<dkim>
    TXT @ v=spf1 include:spf.unione.io ~all
    CNAME _dmarc the dmarc value
  3. domain/validate-verification-record, then domain/validate-dkim.

  4. domain/list — wait until the verification record is confirmed and DKIM is active.

  5. Read from_email from your config, so switching from the sandbox needs no code change.

Sending reliably

  • Retries: set message.idempotence_key on sends that may be retried, so a repeated request does not send twice.
  • Limits: up to 500 recipients and 10 MB per request; each attachment up to 7 MB.
  • Body: always include body.html or body.plaintext.
  • Delivery status: register a webhook (webhook/set) with a fast, idempotent HTTPS handler instead of polling.
  • Defaults: keep the default unsubscribe footer and tracking; changing them requires UniOne support.
  • Errors: retry 429 and 5xx with backoff; do not retry other errors unchanged.

For package contributors

See AGENTS.md: how this package is generated from the OpenAPI spec, built, tested and released.

Metadata

Release files for unione-django 1.1.3

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-django 1.1.3
File Size Uploaded Provenance Actions
unione_django-1.1.3.tar.gz 11.6 kB With provenance Details for unione_django-1.1.3.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for unione-django 1.1.3
File Interpreter ABI Platform Actions
unione_django-1.1.3-py3-none-any.whl Python 3 none any Details for unione_django-1.1.3-py3-none-any.whl

Total release size: 25.5 kB

Release files / unione_django-1.1.3.tar.gz

Download URL unione_django-1.1.3.tar.gz
Size 11.6 kB
Tags Source
SHA-256 checksum
How to use checksums
8fa7791f9e3d64a87d3e278e7f330cfb78b27cf8a0b82c9044c55aaeb998eb40
BLAKE2b-256 checksum
How to use checksums
45e0ecc55b0525d8835b9ff37e5d2ec238ed24387aa5259b1f4273498352076d
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 Oct 20, 2026.

Transparency log

Release files / unione_django-1.1.3-py3-none-any.whl

Download URL unione_django-1.1.3-py3-none-any.whl
Size 13.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
289bfa65b1d7f2d5cdff769e59e034676862a1812a8baff1b55dcda24f7d7008
BLAKE2b-256 checksum
How to use checksums
255af383997005ad7ae1bf1563c2e4f074391d319c036e10649037248d37faa0
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 Oct 20, 2026.

Transparency log

Security for unione-django 1.1.3

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_django-1.1.3.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.

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