# `Raxol.Gateway.Adapter.Email.Inbox`
[🔗](https://github.com/DROOdotFOO/raxol/blob/raxol_gateway-v0.2.0/lib/raxol/gateway/adapter/email/inbox.ex#L1)

Polls a mailbox for new messages and hands each raw message to a
caller-supplied function.

This is the inbound *feed* for `Raxol.Gateway.Adapter.Email`: the stateful
loop that turns a mailbox into a stream of raw RFC822 messages. It is
transport-agnostic on purpose -- `gen_smtp` only speaks SMTP, and the mailbox
a deployment reads (IMAP, POP3, the Gmail API, or an SMTP listener) is its
choice, so the fetch is injected. There is no default `:fetch_fn`, the same
way `Raxol.Gateway.Pipeline.Transcribe` has no default audio fetch: the
transport is environment-specific.

It mirrors `Raxol.Telegram.UpdatePoller`: sink-agnostic (`:on_message`
decides what a message means), authorize-before-route in the sink, and
exponential backoff on fetch errors. A gateway wiring:

    {:ok, store} = ThreadStore.start_link(name: MyThreads)

    Raxol.Gateway.Adapter.Email.Inbox.start_link(
      fetch_fn: fn cursor -> MyImap.fetch_since(cursor) end,
      on_message: fn raw ->
        case Raxol.Gateway.Adapter.Email.normalize_event(raw) do
          {:ok, route, event} ->
            # NOTE: `route` is keyed on the unauthenticated `From` header.
            # authorize is only meaningful behind an MTA that verifies the
            # sender (SPF/DKIM/DMARC) -- see the `Adapter.Email` "Security"
            # section. A raw feed with no upstream auth is anonymous.
            with :allow <- Raxol.Gateway.Pairing.authorize(MyPairing, route) do
              Raxol.Gateway.Adapter.Email.ThreadStore.record_event(store, route, event)
              Raxol.Gateway.SessionRouter.route(MyRouter, route, event)
            else
              :deny -> Logger.info("unauthorized sender denied")
            end

          :ignore ->
            :ok
        end
      end
    )

## Options

  * `:fetch_fn` (required) - `(cursor -> {:ok, [raw], next_cursor} |
    {:error, reason})`. Fetches messages newer than `cursor` and returns the
    cursor to poll from next. `cursor` starts at `:initial_cursor` (default
    `nil`). The transport owns durable cursor state (e.g. an IMAP
    UIDVALIDITY/UID checkpoint), so a restart resumes where the transport
    left off rather than re-reading the mailbox.
  * `:on_message` (required) - 1-arity function called per raw message, in
    order. A crash inside it is caught, logged, and reported as
    `[:raxol_gateway, :email_inbox, :handler_error]` telemetry; the loop
    continues and the cursor still advances (holding it would let one poison
    message stall the feed), so the failed message is dropped -- a consumer
    that needs exactly-once must dedup on `Message-ID`, and an operator should
    alert on that telemetry to catch drops from a transient downstream outage.
  * `:interval_ms` - delay between polls (default 60_000). Applied after each
    completed poll, empty or not.
  * `:max_bytes` - a raw message larger than this is dropped before
    `on_message` (default 10MB), with a `Logger.warning` and
    `[:raxol_gateway, :email_inbox, :oversized]` telemetry. Bounds the memory
    `:mimemail.decode` spends parsing untrusted mail into part trees.
  * `:initial_cursor` - the cursor for the first fetch (default `nil`).
  * `:name` - optional registered name.

## Errors

A `:fetch_fn` error backs off exponentially (1s doubling, capped at 60s) and
resets on the next success. The fetch reason is logged classified only -- the
`:fetch_fn` closure may capture mailbox credentials, so neither it nor the
state is ever dumped (`format_status/1` redacts both).

# `child_spec`

Returns a specification to start this module under a supervisor.

See `Supervisor`.

# `handle_manager_call`

# `handle_manager_cast`

# `start_link`

```elixir
@spec start_link(keyword()) :: GenServer.on_start()
```

---

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