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 thancursorand returns the cursor to poll from next.cursorstarts at:initial_cursor(defaultnil). 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 onMessage-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 beforeon_message(default 10MB), with aLogger.warningand[:raxol_gateway, :email_inbox, :oversized]telemetry. Bounds the memory:mimemail.decodespends parsing untrusted mail into part trees.:initial_cursor- the cursor for the first fetch (defaultnil).: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).
Summary
Functions
Returns a specification to start this module under a supervisor.
Callback implementation for Raxol.Core.Behaviours.BaseManager.handle_manager_call/3.
Callback implementation for Raxol.Core.Behaviours.BaseManager.handle_manager_cast/2.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
Callback implementation for Raxol.Core.Behaviours.BaseManager.handle_manager_call/3.
Callback implementation for Raxol.Core.Behaviours.BaseManager.handle_manager_cast/2.
@spec start_link(keyword()) :: GenServer.on_start()