← Email

Delivery and service

email · workzone

How sending is built: one service with a clear contract, two call modes and honest handling of delivery failures. The consumer knows nothing about SMTP — it asks to “send the invitation”, and the transport takes care of the rest.

Service interface

Inside are two low-level steps: compose (template + data + language → a ready email: subject, HTML and text version) and send (delivery of the ready email over SMTP).

  • send_invite (email, link, role, language) — the invitation email.
  • send_password_reset (email, link, language) — the password-reset email.
  • send_test (email, language) — a diagnostic email for the “Test connection” button.

Inline or queue

One core of compose + send, two call paths. The choice is by the nature of the scenario, not by convenience.

Decision. Diagnostics — inline (synchronous): the test email is sent right in the request with a timeout, so the admin sees the verdict at once. Everything else — through the queue: invitations (including bulk), password reset and alerts are enqueued as a task in SAQ, and a worker sends them in the background.
Inline synchronous
Sending within the request with a short timeout. The outcome is available at once: the result is written to last_test_ok / last_test_at and reflected by a status chip on the settings screen. Who: the test email only.
Queue · SAQ asynchronous
A task in SAQ on top of Redis (the broker is already there). Emails are sent in the background by the platform's shared worker — with retries, throttling and durability across restarts. The consumer replies instantly, without waiting for delivery. Who: invite · reset · bulk · alerts.

Queue and batch sending → Cache & Workers

Send rate is governed by the queue worker — not the consumer. It's the worker's responsibility, in one place: drop the tasks in, and the worker works them off at the set rate.

Delivery errors

SMTP can go down, time out or reject. What happens depends on the mode and on the type of error.

By mode.
inline Verdict at once Failure → a typed error upward; the test screen shows a fail, and false is written to last_test_ok.
queue Retries, then not-delivered A temporary failure → retries with exponential backoff; exhausted → the task goes to “not delivered”, and the status is visible to the consumer (for an invite — on the admission model).
By error type.
5xx Permanent rejection Wrong address, mailbox refused — retries are pointless, straight to “not delivered”.
4xx · network Temporary failure Server unavailable, timeout, provider throttle — we retry with backoff.