@Mail Domain Check

Django Post Office 3.11.2 | SMTP 421 | queued delivery

Django Post Office 3.11.2: SMTP 421 Too Many Connections

Version 3.11.2 deliberately gives each sending thread its own email-backend connection. The useful capacity bound is active commands multiplied by the smaller of queued messages or processes times threads per process. Prove that peak before retrying a production queue.

Already receiving SMTP 421? Keep the queued rows, stop overlapping launches, and reduce the measured connection budget. Contain the queue safely

Decision: budget connections, not only batch size

In 3.11.2, _send_bulk() starts at most min(THREADS_PER_PROCESS, email_count) worker threads, and _send_email() obtains the configured connection inside its worker. The documented default for THREADS_PER_PROCESS is 5. The management command can also start multiple processes.

per_run_slots = min(queued_emails, processes * threads_per_process)
potential_peak = active_runs * per_run_slots

active_runs must include overlapping scheduler invocations and independently scheduled containers or replicas. A lock file stored on each container's private filesystem does not serialize the other replicas.

Browser-local capacity budget

Compare the modeled peak with the provider cap

No SMTP host, sender, recipient, credential, IP, queue content, provider payload, configuration file, or raw log is requested or transmitted.

What changed in 3.11.2

The 3.11.2 release says that _send_email should dispatch mail with its own connection. The merged change resolves connections[email.backend_alias or "default"] in the worker thread and passes it into Email.dispatch(). Its regression test changed from expecting one shared backend connection to expecting one backend per thread.

This addresses cross-thread use of a backend that is not guaranteed to be thread-safe. Downgrading to 3.11.1 can reduce connection creation, but it also restores the behavior that the change was intended to fix. A measured concurrency limit on 3.11.2 is the safer first containment.

Contain the queue without creating a retry storm

  1. Pause overlapping scheduler launches while leaving queued rows intact.
  2. Set POST_OFFICE["THREADS_PER_PROCESS"] = 1 and invoke send_queued_mail --processes=1.
  3. Prove one deployment-wide scheduler owner. If each replica has a private filesystem, move serialization to a shared lock, leader election, or a single dedicated worker.
  4. Submit one controlled queued message and count active SMTP connections around connect, dispatch, and close.
  5. Increase one dimension at a time only after the measured peak remains below the provider limit.

SMTP 421 is a transient service-unavailable response. Preserve or requeue the current work with bounded exponential backoff and jitter. Do not let every scheduler, worker, and user retry create another immediate fan-out.

A local lock is not a deployment-wide lock

Django Post Office wraps queue sending in a file lock, but the guarantee follows the filesystem. Processes sharing the same lock path and filesystem can serialize; containers with isolated writable layers can each acquire their own file and run at the same time.

Record scheduler owner, deployment or replica ID, command start and finish UTC time, claimed email count, and bounded active-connection count. That is enough to prove overlap without publishing queue contents, addresses, credentials, hostnames, IPs, or raw exception logs.

Prove the provider budget in a regression test

Use a test backend whose connection context increments a thread-safe active counter, waits on a barrier long enough for workers to overlap, records the peak, and decrements in finally. Run the command with the intended process and thread settings, plus an intentional second scheduler invocation.

The release gate is not merely that every queued email was attempted. Assert that the peak never exceeds the configured provider budget, each queued row reaches one bounded outcome, and retryable failures do not create duplicate active attempts.

Check public sender controls only after one controlled submission

A public scan can verify MX, SPF, DKIM, DMARC, PTR, and transport-policy signals. It cannot see a private Post Office queue, scheduler overlap, the provider's connection cap, or which thread opened which socket.

Primary references