Decides whether an outgoing message is sent inline or handed to Oban.
Core calls PhoenixKit.Modules.Emails.Provider.maybe_enqueue/2 (the optional
PhoenixKit.Email.Provider callback) right after interception, on both
delivery paths. That placement is the whole point: the host application's own
mail — password resets, confirmations, anything sent through its statically
configured mailer — goes through the same gate as mail sent through this
package, so "everything that leaves the app" is one queue and one log.
What is queued
Nothing, unless the email system itself is enabled (email_enabled) — a
disabled system must not change how mail is sent, only stop recording it. On
top of that:
email_queue_enabled(default true) is the master switch;- authentication mail (core tags it
campaign_id: "authentication") is sent inline unlessemail_queue_auth_mailis turned on. Queueing a password reset means the user waits for a worker to pick it up, and a stuck queue turns into "I never got the email" — the one case where the latency is worse than the throughput is worth; - a caller may always opt out per message with
queue: false; - messages with attachments are sent inline. The job carries the message as JSON args, and attachment payloads have no business in the jobs table;
- nothing is queued unless the host application actually declared the
:emailsOban queue (runnable?/0) — see the warning in that function.
Each of these is a :continue, i.e. "not mine, send it now" — never an error.
A queue that swallows mail on a bad day is worse than one that never engages.
Callers that pick their own transport must opt out
The queue offer sits inside both of core's delivery paths, including
PhoenixKit.Mailer.deliver_via_integration/3 — but the opts core hands us do
not carry the integration uuid that call was made with, only its :provider
string. SendJob therefore re-sends through deliver_email/2, which resolves
the default integration. For a send routed at an explicitly chosen
connection (a newsletter send profile, a per-tenant relay) that is a silent
change of transport, so such callers must pass queue: false until core
carries the uuid into the job. Sends that were already going out through the
default integration are unaffected — it is the same resolution either way.
Summary
Functions
Whether authentication mail is queued too — email_queue_auth_mail, defaults to false.
Rebuilds the Swoosh.Email a serialize/1 produced.
Turns serialize_opts/1 output back into the keyword list the mailer takes.
Master switch — email_queue_enabled, defaults to true.
Returns {:queued, log_uuid} when the message was handed to Oban, :continue
when the caller should send it on this process.
Whether the host application actually declared the :emails Oban queue.
Serializes a Swoosh.Email into JSON-safe job args.
Sets email_queue_auth_mail.
Sets email_queue_enabled.
Explains, in one atom, why the queue would not take the next message — for the settings page, which otherwise has to guess.
Functions
@spec auth_mail_enabled?() :: boolean()
Whether authentication mail is queued too — email_queue_auth_mail, defaults to false.
@spec deserialize(map()) :: Swoosh.Email.t()
Rebuilds the Swoosh.Email a serialize/1 produced.
Turns serialize_opts/1 output back into the keyword list the mailer takes.
@spec enabled?() :: boolean()
Master switch — email_queue_enabled, defaults to true.
@spec maybe_enqueue( Swoosh.Email.t(), keyword() ) :: :continue | {:queued, String.t()}
Returns {:queued, log_uuid} when the message was handed to Oban, :continue
when the caller should send it on this process.
@spec runnable?() :: boolean()
Whether the host application actually declared the :emails Oban queue.
Load-bearing, not diagnostic: Oban.insert/1 happily accepts a job for a queue
nobody runs, so on a host that never added emails: N the message would be
stored and never sent — the queue would look enabled and quietly swallow the
mail. We therefore refuse to queue at all unless the host can drain it.
Reads the parent app's compile-time Oban config, which is how PhoenixKit
installs Oban. A host that declares queues as a map, or starts Oban at runtime
with Oban.start_link/1, reads as "not runnable" here — that is the safe
direction (inline send, never a stuck queue), not a mail outage.
@spec serialize(Swoosh.Email.t()) :: map()
Serializes a Swoosh.Email into JSON-safe job args.
Only the fields a send needs: recipients, subject, bodies and headers. The tracking header rides along, which is what makes the worker's send reuse the log row this message already has instead of writing a second one.
Deliberately dropped: attachments (queued mail with attachments is sent
inline instead — see the moduledoc), and provider_options / assigns /
private, which are not JSON-safe in the general case. Nothing sets
provider_options per-email today; if that changes, round-trip it here or
queued mail will silently lose options that an inline send keeps. (The SES
configuration set and message tags travel as headers, so those are kept.)
Recipients and bodies land in oban_jobs
Queued mail carries its full body in the job's args until the row is
pruned — independent of email_save_body and of email_retention_days,
which only govern this module's own tables. A deployment that keeps
completed jobs forever keeps the message content forever; configure
Oban.Plugins.Pruner accordingly.
Sets email_queue_auth_mail.
Sets email_queue_enabled.
@spec status() :: :ok | :system_disabled | :queue_disabled | :no_oban_queue
Explains, in one atom, why the queue would not take the next message — for the settings page, which otherwise has to guess.
:ok means it would.