Skip to main content

thinwrap-notifications (Python)

Unified, SDK-free, zero-runtime-dependency Python wrapper for sending email, SMS, push, and chat across 35 providers. Switch vendor by changing the config type; the input and output shapes stay identical. Synchronous, stateless, and standard-library-only — bring your own HTTP transport if you want httpx/requests.

pip install thinwrap-notifications

Requires Python ≥ 3.10. No runtime dependencies — AWS SigV4 (SES/SNS) and JWT signing (FCM RS256, APNs ES256) are implemented in pure Python on the stdlib.

Two-minute send

from thinwrap.notifications import Email, EmailSendInput, SendgridConfig, ConnectorError

e = Email(SendgridConfig(api_key="...", from_="hello@acme.com"))
try:
    res = e.send(EmailSendInput(
        to="user@example.com",
        subject="Welcome",
        text="Thanks for signing up!",
        html="<p>Thanks for signing up!</p>",
    ))
    print(res.status.value, res.provider_message_id)
except ConnectorError as e:
    print(e.provider_code.value, e.provider_message)

from is a Python keyword, so the sender field is from_ (config default and per-send override).

Channels & providers

The config type selects the provider. A provider that doesn't serve a channel raises ValueError at facade construction (e.g. Email(TwilioConfig(...))).

Facade Method Providers
Email send SES, Resend, Mailgun, SendGrid, Postmark, MailerSend, Mailtrap, Brevo, SparkPost, Scaleway
Sms send Vonage, Twilio, Plivo, SNS, Sinch, Telnyx, Infobip, MessageBird, TextMagic, D7 Networks
Push send FCM, Expo, APNs, OneSignal, Pusher Beams, WonderPush
Chat send Telegram, Slack, WhatsApp Business, Discord, MS Teams, Google Chat, Mattermost, Rocket.Chat, LINE

Per-provider details (endpoint, auth, error mapping, passthrough keys) live in docs/providers/ — one page per provider.

Switching providers

# Same send() call, same EmailSendResult — only the config changes.
e = Email(ResendConfig(api_key="...", from_="hi@acme.com"))
# or PostmarkConfig(...), SesConfig(...), ...

The normalized surface

Every send returns a frozen <Channel>SendResult:

@dataclass(frozen=True)
class EmailSendResult:
    success: bool
    status: SendStatus            # sent | queued | rejected | suppressed | unknown
    provider_message_id: str
    raw: Any                      # decoded vendor body, verbatim

Only fields ≥90% of a channel's providers support are first-class on the input (the ≥90% baseline-coverage rule). Provider-specific fields flow through the Passthrough escape hatch:

from thinwrap.notifications import Sms, SmsSendInput, TwilioConfig, Passthrough

s = Sms(TwilioConfig(account_sid="...", auth_token="...", from_="+15550001111"))
s.send(SmsSendInput(
    to="+15552223333",
    body="hi",
    passthrough=Passthrough(body={"mediaUrl": ["https://cdn/img.png"]}),  # Twilio MMS
))

passthrough.body is deep-merged into the request body, headers/query shallow-merged; consumer values win on collision.

Errors

A failed send raises ConnectorError: a vendor error response, a transport failure, or request data the connector rejects before sending (a missing recipient, an invalid region, a header the vendor forbids, a header value with a line break, a value that cannot be encoded) all arrive this way, as invalid_request, which is never worth retrying. Misusing the API itself raises a native exception when the facade is built: a missing or non-config argument is a TypeError, and a config for another channel (e.g. Sms(SendgridConfig(...))) is a ValueError.

try:
    ...
except ConnectorError as e:
    # e.provider_code  — ProviderCode: invalid_recipient | rate_limited | auth_failed |
    #                    provider_unavailable | invalid_request | unknown
    # e.status_code    — int | None (None for transport failures)
    # e.provider_message
    # e.cause          — {"raw": <vendor body>, "retryAfter": <hdr>, "retryAfterSeconds": <n>}
    ...

There is deliberately no retry_after_seconds attribute — the raw Retry-After header and its parsed seconds ride in cause. Retry is consumer policy.

Bring your own transport

from thinwrap.notifications import Email, SendgridConfig

e = Email(SendgridConfig(api_key="..."), transport=my_transport)

my_transport implements Transport (a send(HttpRequest) -> HttpResponse method). The default stdlib urllib transport never follows redirects. repr(HttpRequest) hides header values, the URL path and the body, so a transport that logs requests does not log credentials; the request still holds them, and so do the local variables of the frames in a traceback, so an error tracker that captures frame locals needs its own scrubbing. The wrapper is stateless: no caching, retries, or telemetry. FCM/APNs sign a fresh token on every send unless you supply a TokenCacheHook on the config. For APNs the cache is required in production: Apple rejects a new provider token more often than once per 20 minutes (429 TooManyProviderTokenUpdates).

Relationship to the siblings

One of four thinwrap notifications libraries sharing a normalized surface and byte-identical provider ids / error codes:

Security

Report vulnerabilities privately — please do not open a public issue. Preferred: a private security advisory on this repository. Alternatively, email security@thinwrap.dev. Include the affected versions and a minimal reproduction if you have one.

A vulnerability in a provider's own API or service belongs to that vendor rather than to this wrapper — please report those upstream.

License

MIT © Dmitry Polyanovsky & contributors

Metadata

Release files for thinwrap-notifications 1.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 thinwrap-notifications 1.2.0
File Size Uploaded Provenance
thinwrap_notifications-1.2.0.tar.gz 120.2 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for thinwrap-notifications 1.2.0
File Interpreter ABI Platform
thinwrap_notifications-1.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 229.3 kB

Release files / thinwrap_notifications-1.2.0.tar.gz

Download URL thinwrap_notifications-1.2.0.tar.gz
Size 120.2 kB
Tags Source
SHA-256 checksum
How to use checksums
efffc75c827cb0a4b1937a3866918648e7e9217fc0685e0729436161f312d1b9
BLAKE2b-256 checksum
How to use checksums
d005944160a71ab085eb93cc2c727eca451c985f4da16418065a2a57ff3da556
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 18, 2026.

Transparency log

Release files / thinwrap_notifications-1.2.0-py3-none-any.whl

Download URL thinwrap_notifications-1.2.0-py3-none-any.whl
Size 109.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d45df49c822de2ea56f8026b9067650bbf1193107bfcbd720db5cf25a777946c
BLAKE2b-256 checksum
How to use checksums
ac01d1df5006cc22fd5160f5579664f02bced009bdd4b432e0ccc199429c5777
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 18, 2026.

Transparency log

Security for thinwrap-notifications 1.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

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

Select from 2 files

Attestation for thinwrap_notifications-1.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 Oct 18, 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

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