poulpinou (0.1.0.dev0)
Installation
pip install --index-url poulpinouAbout this package
Envoi de mails Django asynchrone via Celery, backend drop-in, IHM d'admin pour la file d'attente.
poulpinou
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 optionalPOULPINOU_*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
PoulpinouEmailBackend.send_messages()receives theEmailMessageobjects Django normally builds, and stores them as-is (subject, text/HTML body, recipients, attachments, headers) in thepoulpinou.Mailmodel, statuspending. That's the only thing that happens synchronously — no mail is ever sent "inline".- The periodic task
poulpinou.tasks.dispatch_pending_mails(scheduled via django-celery-beat, see below) picks up a batch ofpendingmails and hands them tosend_one. send_onerebuilds the originalEmailMessageand sends it viadjango.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 toPOULPINOU_MAX_RETRIES, then move to statusfailed.
Admin UI (Django admin)
- Queue:
Poulpinou > Mails en file d'attentelists 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_errorand the retry count; a "Remettre en attente" (requeue) action to resend them. - Send frequency: if
django-celery-beatis installed, poulpinou automatically creates, on the firstmigrate, a periodic taskpoulpinou: dispatch pending mails(everyPOULPINOU_SEND_INTERVAL_SECONDSseconds, 60 by default). It's then editable directly fromPeriodic tasksin the Django admin (interval or cron expression) — no code redeploy needed to change the frequency. Withoutdjango-celery-beat, the task has to be wired intoCELERY_BEAT_SCHEDULEby 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.