---
title: "Sending transactional email from a Python app with PulsePigeon"
canonical: https://pulsepigeon.com/blog/send-email-from-python
last_updated: 2026-08-26
---

# Sending transactional email from a Python app with PulsePigeon

The Python SDK pattern: synchronous and asynchronous clients, deterministic idempotency keys, status-specific errors, and application-owned retries.

PulsePigeon's published Python SDK includes `PulsePigeonClient` and `AsyncPulsePigeonClient` interfaces. Install version 1.0.0 from PyPI for the project-key surface.

For Django views, the sync client fits naturally: the view returns a response only after the email send has been accepted or rejected. Use the sync client when the email is critical to the user-facing flow (email verification, password reset).

The verified `AsyncPulsePigeonClient.send_message` can serve an asynchronous application boundary. For a conventional Celery worker, the synchronous interface is usually simpler. In either case, the worker configuration, not the SDK, owns retries and backoff.

Idempotency keys in Python should derive from the event that triggered the send. For a Django password reset, the reset request id is the key; for an order confirmation, the order id. The SDK accepts the key via the `idempotency_key=` kwarg.

Catch the documented classes instead of invented permanent/transient names. For example, correct `PulsePigeonValidationError` and `PulsePigeonConflictError`, honor `PulsePigeonRateLimitError.retry_after_seconds`, and apply bounded backoff to `PulsePigeonServerError`. Keep the [HTTP API error reference](/docs/errors) and [SDK method reference](/docs/sdks) beside that error-handling code.
