Billing guide

Smtp email adapter

SMTP invoice email adapter

The SMTP adapter is an optional transport behind the durable invoice-email delivery state machine. It uses the bundled mail 2.9.1 and net-smtp 0.5.1 gems. It does not implement delivery-status lookup or reconciliation: ordinary SMTP cannot authoritatively query a prior submission by the client Message-ID.

Activation and configuration

Configure SMTP in Systemeinstellungen at /admin/system_settings using a system administrator account. Stored global configuration takes precedence over environment variables, including a stored disabled state. Global SMTP and the merchant’s InvoiceEmailDeliveryEnabled setting must both be enabled. See /docs/system-smtp-settings for password handling, permissions and auditing.

Only while no global database configuration has been saved, all three fallback gates apply:

  1. global BILLING_INVOICE_EMAIL_DELIVERY_ENABLED=true;

  2. merchant setting InvoiceEmailDeliveryEnabled=true;

  3. BILLING_INVOICE_EMAIL_ADAPTER=smtp with a valid adapter configuration.

The environment fallback constructor reads:

Variable Requirement
BILLING_SMTP_HOST Required DNS hostname; no scheme, path, credentials, or port.
BILLING_SMTP_PORT Optional, defaults to 587; must be 1–65535.
BILLING_SMTP_HELO_DOMAIN Required DNS hostname used for EHLO/HELO.
BILLING_SMTP_USERNAME Required; passed only to SMTP authentication.
BILLING_SMTP_PASSWORD Required; passed only to SMTP authentication.
BILLING_SMTP_ALLOWED_FROM Required exact sender address for this installation.
BILLING_SMTP_TLS_MODE starttls by default or tls; no plaintext/automatic downgrade mode.

Billing::SmtpInvoiceEmailAdapter.from_env validates configuration without opening DNS or a socket. The object redacts its inspection string. Do not enable Net::SMTP debug output: it can expose authentication and message contents.

TLS and authentication

Submission uses either mandatory STARTTLS (starttls: :always) or implicit TLS. Both modes enable certificate and hostname verification and require TLS 1.2 or newer. Open and read timeouts are bounded; a write timeout is set only when the injected transport exposes that capability. Authentication is SASL PLAIN inside the verified TLS session; LOGIN and CRAM-MD5 are not enabled.

The implementation follows the installed/upstream interfaces:

MIME contract

The adapter creates one multipart message with fixed From, To, Subject, Date, Message-ID, and MIME headers; a UTF-8 plain-text body; and exactly one base64 application/pdf attachment. The stable client Message-ID derives from the durable dispatch ID. No Bcc, HTML, custom headers, filesystem path, credential, or raw provider data is added.

The adapter receives attachment bytes only in memory after the delivery service has verified the immutable IssuedDocument and manifest. It never persists the generated raw MIME message.

Outcomes

If send_message returns successfully and closing the session later fails, the result remains Sent: the server already accepted the DATA transaction. DNS, TCP, TLS, or authentication failures before message invocation are fixed rejections. The adapter deliberately exposes no reconcile method; an unknown SMTP submission requires operator/provider evidence outside this adapter.

Production gates

Keep the adapter disabled until credentials are provisioned through the deployment secret store, the allowed sender and DNS/TLS policy are reviewed, and an isolated staging rehearsal covers acceptance, explicit rejection, timeout after DATA begins, close failure, bounce processing, and unknown-state operations. Bounce and delivered notifications require a separate inbound provider integration and must not rewrite Sent without durable evidence.

Local verification

BILLING_DB_PORT=55440 sh bin/test-local \
  test/services/billing/invoice_email_delivery_test.rb \
  test/services/billing/invoice_email_delivery_concurrency_test.rb \
  test/services/billing/smtp_invoice_email_adapter_test.rb \
  test/jobs/invoice_email_delivery_job_test.rb

24 runs, 254 assertions, 0 failures, 0 errors, 0 skips

The adapter tests use an injected session/factory only. They parse the generated MIME message, round-trip the base64 PDF bytes, inspect TLS and authentication arguments, and simulate explicit SMTP rejection, lost response, mandatory-STARTTLS failure, and failure after accepted DATA. No DNS lookup, socket, credential, provider, production address, or real email is used.