PhoenixKit.Modules.Emails.Queue (phoenix_kit_emails v0.1.19)

Copy Markdown View Source

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 unless email_queue_auth_mail is 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 :emails Oban 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

auth_mail_enabled?()

@spec auth_mail_enabled?() :: boolean()

Whether authentication mail is queued too — email_queue_auth_mail, defaults to false.

deserialize(data)

@spec deserialize(map()) :: Swoosh.Email.t()

Rebuilds the Swoosh.Email a serialize/1 produced.

deserialize_opts(opts)

@spec deserialize_opts(map()) :: keyword()

Turns serialize_opts/1 output back into the keyword list the mailer takes.

enabled?()

@spec enabled?() :: boolean()

Master switch — email_queue_enabled, defaults to true.

maybe_enqueue(email, opts)

@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.

runnable?()

@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.

serialize(email)

@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.

set_auth_mail_enabled(value)

@spec set_auth_mail_enabled(boolean()) :: {:ok, term()} | {:error, term()}

Sets email_queue_auth_mail.

set_enabled(value)

@spec set_enabled(boolean()) :: {:ok, term()} | {:error, term()}

Sets email_queue_enabled.

status()

@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.