Behavior
SMTP sending is policy-gated before Arbiter opens an SMTP transaction. The caller selects a configured account and message payload; Arbiter supplies the transport, credentials, sender identity, recipient checks, rate limits, and safe retry handling from deployment config.
Sent Copies
By default, SMTP attempts to save a copy of successfully submitted messages to
IMAP Sent mail when the destination can be resolved unambiguously. The SMTP
account id is reused as the IMAP account id. For example,
smtp:send_email(account="personal") only considers imap.personal.
Arbiter serializes the message once and uses those same bytes for SMTP DATA and the IMAP append, so the Sent copy matches what was submitted to the SMTP server.
Normal configuration is to mark exactly one folder on the matching IMAP account
as kind: SENT:
folders:
Sent:
description: Sent mail.
kind: SENT
If there is no matching IMAP account, no kind: SENT folder, or multiple
kind: SENT folders, the sent copy is skipped unless a folder override is set
on the SMTP account:
sent_copy:
folder: "Sent Messages"
SMTP policy controls whether sent-copy is attempted and how failures are reported:
sent_copy:
enabled: true
on_failure: warn # warn | fail
With on_failure: warn, SMTP submission success remains success and the
operation result includes sent_copy.status: failed, skipped, or unknown.
unknown means the IMAP append may have succeeded before its outcome was lost.
With on_failure: fail, Arbiter fails before SMTP submission when the destination
cannot be resolved. If SMTP succeeds but IMAP append fails, the operation fails
with an explicit error; the email has already been submitted and cannot be
unsent.
Safe Retries
SMTP envelope addresses must be ASCII because the client does not request SMTPUTF8. Unicode display names and message content remain supported. Unsupported envelope addresses are rejected before reserving a retry key.
Sending email is not reversible, so retries need a way to avoid accidentally
sending the same message twice. Arbiter does this with a caller-provided retry
key. This is the behavior the config calls idempotency.
Provide idempotency_key when retrying a send. The SMTP plugin stores keyed
records in its plugin data directory and replays a successful result for the
same key and payload until the retry record expires.
While the original retry record remains valid, a retry with the same
idempotency_key and payload does not submit the message through SMTP again.
After a definitive Sent-copy failure or a skipped copy, Arbiter reuses the
stored submitted message bytes and retries only the IMAP Sent copy. Until that
retry succeeds or the record expires, the record can contain the submitted
message bytes.
Failures during connection or session setup happen before SMTP submission and release the retry key. Transport failures during submission retain the key because delivery may already have occurred. If repeated or case-colliding recipient addresses produce refusals after DATA, the server response cannot prove that every occurrence was rejected; Arbiter reports an uncertain delivery outcome and retains the key to prevent an accidental resend.
A 421 reply during RCPT processing stops the transaction before DATA, so no
message is submitted and the retry key is released. In that error's details,
not_submitted_count covers the entire recipient list, while rejected_count
counts only distinct addresses explicitly refused before the abort. These
counts overlap: recipients not processed, or accepted at RCPT before the abort,
are also included in not_submitted_count.
An uncertain append has sent_copy.status: unknown. Keyed replay preserves
that outcome and does not repeat the append, since the copy may already exist.
Inspect the destination before attempting recovery. Arbiter does not
automatically retry operations.
Arbiter stores SMTP idempotency records under the SMTP plugin's server-managed writable data directory. Operators control persistence by preserving plugin data; callers only provide retry keys.