# `PhoenixKit.Modules.Emails.Queue`
[🔗](https://github.com/BeamLabEU/phoenix_kit_emails/blob/0.4.2/lib/phoenix_kit/modules/emails/queue.ex#L1)

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.

# `auth_mail_enabled?`

```elixir
@spec auth_mail_enabled?() :: boolean()
```

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

# `deserialize`

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

Rebuilds the `Swoosh.Email` a `serialize/1` produced.

# `deserialize_opts`

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

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

# `enabled?`

```elixir
@spec enabled?() :: boolean()
```

Master switch — `email_queue_enabled`, defaults to true.

# `maybe_enqueue`

```elixir
@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?`

```elixir
@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`

```elixir
@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` {: .warning}
>
> 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`

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

Sets `email_queue_auth_mail`.

# `set_enabled`

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

Sets `email_queue_enabled`.

# `status`

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

---

*Consult [api-reference.md](api-reference.md) for complete listing*
