Skip to main content

coss-django

A Django email backend that sends your app's email through the coss.com API. Switch by changing settings, not call sites: send_mail, EmailMessage, mail_admins, password reset emails and every other Django email keep working.

Docs: coss.com/docs/integrations/django

Django 5.2–6.1, Python 3.10+. No dependencies besides Django.

Install

pip install coss-django   # or: uv add coss-django

Settings

# settings.py
EMAIL_BACKEND = "coss_django.EmailBackend"
COSS_API_KEY = env("COSS_API_KEY")  # coss_live_… or coss_test_…
DEFAULT_FROM_EMAIL = "Acme <hello@acme.com>"  # a verified domain of yours

On Django 6.1, which configures email with MAILERS, put the same options in OPTIONS:

MAILERS = {
    "default": {
        "BACKEND": "coss_django.EmailBackend",
        "OPTIONS": {"api_key": env("COSS_API_KEY")},
    },
}
Setting Backend option Default
COSS_API_KEY api_key the COSS_API_KEY environment variable
COSS_BASE_URL base_url the COSS_BASE_URL environment variable, else https://api.coss.com
COSS_TIMEOUT timeout 30 (seconds, per attempt)
COSS_MAX_RETRIES max_retries 2

Options passed to the backend (get_connection("coss_django.EmailBackend", api_key=…) or MAILERS OPTIONS) win over the settings. A missing API key fails the first send with ImproperlyConfigured, never your app's startup.

Usage

Nothing changes:

from django.core.mail import EmailMultiAlternatives, send_mail

send_mail("Welcome", "Thanks for signing up!", None, ["ada@example.com"])

message = EmailMultiAlternatives(
    subject="Your receipt",
    body="Thanks for your order!",
    to=["Ada Lovelace <ada@example.com>"],
    reply_to=["support@acme.com"],
    headers={"List-Unsubscribe": "<https://acme.com/unsubscribe>"},
)
message.attach_alternative("<p>Thanks for your order!</p>", "text/html")
message.attach_file("receipt.pdf")
message.send()

message.coss_email["id"]  # "email_…": the email in coss.com (API, dashboard)

Each message is one request (POST /v1/emails). After a successful send, message.coss_email holds the email as the API returned it. send_messages() returns the number of messages coss.com accepted; messages without recipients are skipped, as Django's SMTP backend does.

Inline images are attachments with a Content-ID (<img src="cid:logo@acme"> in the HTML):

from email.message import MIMEPart  # Django 6.0+; on 5.2 use email.mime.image.MIMEImage

logo = MIMEPart()
logo.set_content(
    png_bytes,
    maintype="image",
    subtype="png",
    disposition="inline",
    filename="logo.png",
    cid="<logo@acme>",
)
message.attach(logo)

coss.com options

Set coss on a message:

from datetime import datetime, timedelta, timezone

message.coss = {
    "category": "receipts",  # suppressions per category
    "tags": {"plan": "pro", "order": "42"},  # or [{"name": "plan", "value": "pro"}]
    "scheduled_at": datetime.now(timezone.utc) + timedelta(hours=1),  # or an ISO 8601 string
    "tracking": {"opens": True, "clicks": False},  # and "unsubscribe" (one-click)
    "idempotency_key": "order-42",  # a retry with the same key never sends twice
}
message.send()

Unknown keys and invalid values raise ValueError. A datetime is sent in UTC (YYYY-MM-DDTHH:MM:SSZ, fractions dropped); a naive one is taken to be in Django's current time zone (TIME_ZONE, unless you activated another one). A string is sent as given. Without an idempotency_key, each send gets a random one, reused by its retries.

Errors

When coss.com refuses an email or can't be reached, the send raises coss_django.CossError:

from coss_django import CossError

try:
    message.send()
except CossError as error:
    error.code  # e.g. "domain_not_verified"
    error.type  # "invalid_request_error", …, "connection_error", "timeout_error"
    error.status  # HTTP status, None without a response
    error.request_id  # "req_…", quote it to support
    error.message  # the API's message
    error.param  # the field it is about, when there is one
    error.issues  # [{"path": "to", "message": "…"}] for validation errors

str(error) reads coss.com could not send the email: <message> (<code>). Network errors, timeouts, 409, 429 and 5xx responses are retried with backoff (honouring Retry-After up to 60 seconds) before that. With fail_silently=True, these failures and a missing API key are swallowed and the message isn't counted.

What coss.com can't send raises ValueError before anything is sent, fail_silently or not (like a bad header with Django's SMTP backend):

  • alternatives other than one text/html part, or a content_subtype other than plain/html;
  • a Bcc header, a Sender or Return-Path other than from_email, and From, To or Cc headers that name other addresses than the message's from_email, to or cc (Django sends those to the SMTP server separately; coss.com sends one message to the addresses it shows — matching headers are fine, and their display names are used);
  • a subject with a line break, no to address (only cc or bcc), a header given twice, and multipart attachments.

A Reply-To header replaces reply_to, as Django documents. Headers coss.com sets itself (Message-ID, Date, Content-Type, …) are dropped; non-ASCII header values are sent as RFC 2047 encoded-words.

Tests

Django's test runner swaps in the locmem backend, so your tests never call coss.com.

License

MIT

Metadata

Release files for coss-django 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 coss-django 0.1.0
File Size Uploaded Provenance
coss_django-0.1.0.tar.gz 15.9 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for coss-django 0.1.0
File Interpreter ABI Platform
coss_django-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 33.8 kB

Release files / coss_django-0.1.0.tar.gz

Download URL coss_django-0.1.0.tar.gz
Size 15.9 kB
Tags Source
SHA-256 checksum
How to use checksums
db41127c0043d7005589ae4dd0f37151a9a29dd0b9359f1190d410b7c1279543
BLAKE2b-256 checksum
How to use checksums
648c14a942988e1ec3fc43b3c6e59c84e2bf701f1a4d054732b41727ab5c7754
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 16, 2026.

Transparency log

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

Download URL coss_django-0.1.0-py3-none-any.whl
Size 17.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9b39f226633d466dbc5242559970eb40db70780dd9868664f9d36c7cd062b648
BLAKE2b-256 checksum
How to use checksums
455c6492a27b191fe835695bc5eb54184e77555082b993ee3ce9fcc03b929eae
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 16, 2026.

Transparency log

Security for coss-django 0.1.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

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for coss_django-0.1.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 Oct 16, 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.1.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