LemonChannels.Adapters.Email.Outbound (lemon_channels v0.1.0)

View Source

Sends mail for the email channel adapter over an SMTP relay.

Ported from LemonGateway.Transports.Email.Outbound. smtp_options/1 is the same function it always was — its configuration semantics are pinned by characterization tests that were written before the move and carried over unchanged, so a difference there is a regression rather than a design choice.

What the port had to change

The gateway version was handed a finished run (deliver(job, completed)) and read its reply headers out of job.meta.email_reply, which the inbound half had put there. A LemonChannels.Plugin adapter is handed a LemonChannels.OutboundPayload instead, which carries the recipient, the thread and the id being replied to — but deliberately not a Subject or a References chain, since neither means anything to a channel that is not email. Those come from LemonChannels.Adapters.Email.ThreadStore, which the inbound half populates; see its moduledoc for why that state is worth keeping.

Two behaviours did not survive, both because they described the old entrypoint rather than email:

  • The job.meta guard that made deliver/2 a silent no-op for non-email runs. deliver/1 is only ever called for payloads addressed to this channel, so a payload it cannot send is now an {:error, reason} — an unsendable message should be reported, not swallowed.
  • The "Attachment references" list appended to the body from the run's output files. In this pipeline a file is its own :file payload and is attached to its own message, so there is nothing to reference.

Summary

Functions

The reply headers payload would be sent with.

Sends payload as a reply on its thread.

Builds :gen_smtp_client options from an email config map.

Functions

build_envelope(payload, cfg \\ nil)

(since phase 2.4)
@spec build_envelope(LemonChannels.OutboundPayload.t(), map()) ::
  {:ok, map()} | {:error, term()}

The reply headers payload would be sent with.

Public so the threading decisions — which subject a reply carries, which ancestors end up in References — are assertable without an SMTP server standing by. Not part of the adapter's contract with the platform.

deliver(payload)

@spec deliver(LemonChannels.OutboundPayload.t() | term()) ::
  {:ok, binary()} | {:error, term()}

Sends payload as a reply on its thread.

Returns {:ok, message_id} with the Message-ID of the sent mail, which is also recorded against the thread so the recipient's reply threads back.

smtp_options(cfg)

@spec smtp_options(map() | term()) :: {:ok, keyword()} | {:error, term()}

Builds :gen_smtp_client options from an email config map.

{:error, :missing_smtp_relay} when no relay is configured, which is the common case: this adapter is inert until someone points it at a relay.