poulpinou (1.0.0)

Published 2026-09-29 18:59:36 +00:00 by Gilles in Gilles/poulpinou-django

Installation

pip install --index-url  poulpinou

About this package

Envoi de mails Django asynchrone via Celery, backend drop-in, IHM d'admin pour la file d'attente.

poulpinou

CI License: MIT

Asynchronous Django mail sending, via Celery, without touching existing code. An app that already uses django.core.mail.send_mail or EmailMessage keeps working exactly the same: poulpinou installs by swapping EMAIL_BACKEND, queues mails in the database, then actually sends them in the background (with rate limiting and retries) through Django's own mail system (SMTP, or whatever backend the project already uses).

Why

  • Calling code (send_mail(...), EmailMultiAlternatives(...).send(), Django forms, etc.) needs no change at all.
  • The project's existing SMTP configuration (EMAIL_HOST, EMAIL_PORT, EMAIL_HOST_USER, ...) stays unchanged — poulpinou reuses it for actual delivery.
  • A developer integrating poulpinou only adds poulpinou's own configuration (INSTALLED_APPS, EMAIL_BACKEND, a few optional POULPINOU_* settings).

Installation

pip install poulpinou
# or, to get the send-frequency configuration UI:
pip install "poulpinou[beat]"
# settings.py
INSTALLED_APPS = [
    ...,
    "django_celery_beat",  # optional but recommended, see below
    "poulpinou",
]

# The only change to the existing mail config: the backend.
# EMAIL_HOST / EMAIL_PORT / EMAIL_HOST_USER / ... don't change.
EMAIL_BACKEND = "poulpinou.backend.PoulpinouEmailBackend"
python manage.py migrate

Then start a Celery worker as usual (poulpinou uses @shared_task, so the project's own already-configured Celery app is enough):

celery -A myproject worker -l info
celery -A myproject beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler

That's it: no other code to write.

How it works

  1. PoulpinouEmailBackend.send_messages() receives the EmailMessage objects Django normally builds, and stores them as-is (subject, text/HTML body, recipients, attachments, headers) in the poulpinou.Mail model, status pending. That's the only thing that happens synchronously — no mail is ever sent "inline".
  2. The periodic task poulpinou.tasks.dispatch_pending_mails (scheduled via django-celery-beat, see below) picks up a batch of pending mails and hands them to send_one.
  3. send_one rebuilds the original EmailMessage and sends it via django.core.mail.get_connection(backend=POULPINOU_EMAIL_BACKEND) — Django's standard SMTP backend by default, with the project's SMTP config untouched. On failure: retry with backoff, up to POULPINOU_MAX_RETRIES, then move to status failed.

Admin UI (Django admin)

  • Queue: Poulpinou > Mails en file d'attente lists every mail with its status (pending / sending / sent / failed), a counter banner at the top of the list, filters by status/date, search by subject/recipient.
  • Errors: failed mails show last_error and the retry count; a "Remettre en attente" (requeue) action to resend them.
  • Send frequency: if django-celery-beat is installed, poulpinou automatically creates, on the first migrate, a periodic task poulpinou: dispatch pending mails (every POULPINOU_SEND_INTERVAL_SECONDS seconds, 60 by default). It's then editable directly from Periodic tasks in the Django admin (interval or cron expression) — no code redeploy needed to change the frequency. Without django-celery-beat, the task has to be wired into CELERY_BEAT_SCHEDULE by hand.

Settings (settings.py, all optional)

Setting Default Role
POULPINOU_EMAIL_BACKEND django.core.mail.backends.smtp.EmailBackend Django backend used for actual delivery
POULPINOU_BATCH_SIZE 10 Number of mails processed per dispatch_pending_mails run
POULPINOU_RATE_LIMIT "10/m" Celery rate_limit of the send task
POULPINOU_MAX_RETRIES 5 Number of attempts before moving to failed
POULPINOU_RETRY_DELAY 1800 (30 min) Delay between two attempts, in seconds
POULPINOU_SEND_INTERVAL_SECONDS 60 Default interval for the periodic task created on the first migrate (editable afterwards from the admin)

These settings are read once when the Celery process starts (they feed @shared_task decorators): changing them requires restarting the workers, except for the send frequency, which can be controlled live from the admin once django-celery-beat is installed.

Known limitations

  • Only "classic" attachments ((name, content, mimetype), the vast majority of cases) are queued; an attachment passed as a raw MIME object is skipped with a logged warning.
  • The connection (get_connection) is opened per sent mail — no SMTP connection pooling across mails in the same batch.

Development

Tooling aligned with the other Python modules (e.g. vieuxhibou-django): tox runs tests, lint and checks in isolated environments, make wraps tox for the common commands.

make dev            # venv + pip install -e ".[dev]"
make test           # pytest, via tox
make lint           # pylint (+ pylint-django)
make format-check   # autopep8 --diff (CI)
make format         # autopep8 --in-place (local)
make bandit         # static security analysis
make tox            # all tox environments at once

License

MIT, see LICENSE.

Requirements

Requires Python: >=3.10
Details
PyPI
2026-09-29 18:59:36 +00:00
191
2026-10-27 20:49:21 +00:00
stationmyr
MIT
28 KiB
Assets (2)
Versions (6) View all
0.0.0.dev0 2026-10-30
1.1.0.dev0 2026-10-30
1.2.0 2026-10-26
1.1.0 2026-10-25
0.1.0.dev0 2026-10-24