Skip to content

Changelog

[Unreleased]

Changed

  • BREAKING: the mailer configuration class is now Myth\Postal\Config\Mailer (was Myth\Postal\Config\Email), and MailerManager resolves it by its short name. The old name collided with the framework's legacy Config\Email, which forced resolution by fully-qualified class name — and that form never consults an application subclass, so the documented override path could not work. Applications that published a Config\Email extending the package config should rename it to Config\Mailer and extend Myth\Postal\Config\Mailer. The framework's own legacy Config\Email, used by LegacyEmailAdapter and spark email:test, is unaffected. See ADR 0001.

Added

  • spark publish writes app/Config/Mailer.php, a Config\Mailer extending the package's config, which Postal then resolves in preference to its own default. Re-running publish never overwrites an existing file.

  • Failover transport (failover mailer): a composite that tries an ordered list of child mailers (named under a chain key) and falls through to the next on failure — returning the first success and reporting failure only when every child fails. A child that throws is treated as a failure so the chain advances. MailerManager resolves the child mailers by name and hands the already-built transports to the composite. A failover mailer with no children throws a PostalException.

  • Automatic inline images: the renderer scans the HTML body for <img> sources and embeds the embeddable ones — data: image URIs (decoded in memory) and local image file paths (read at render) — rewriting each reference to a cid: URL and de-duplicating identical sources. Remote (http(s)://) and existing cid: sources are left untouched, and only files that sniff as images are read. On by default; disable per message with Email::$autoEmbedImages = false. Adds Attachment::embedData().
  • Amazon SES transport (ses mailer): delivers through the official aws/aws-sdk-php SesV2 client (SigV4 signing and HTTP owned by the SDK). Sends structured Simple content by default and switches to raw MIME for attachments/inline images, HTML without an explicit text body, or when forceRaw is set. Maps Email::metadata() onto SES EmailTags (dropping and debug-logging tags that break SES's character rules), applies an optional configurationSet, and returns the SES message id. aws/aws-sdk-php is an optional dependency (install it with composer require aws/aws-sdk-php).
  • Email::metadata(key, value) builder for provider tags, mapped by API transports onto their tagging feature and ignored by the others.
  • Attachments and inline images: Email::attach() (file by path, read lazily at render), Email::attachData() (raw bytes), and Email::embedImage() (inline CID image referenceable from HTML), backed by the Attachment value object. The renderer nests multipart/mixed (attachments) around multipart/related (inline images) around the existing multipart/alternative, with base64 parts chunked at 76 columns and part headers sanitised against filename injection.
  • Core send pipeline: compose an Email and send it through a transport via service('mailer'), receiving a SendResult.
  • Email message builder with from/replyTo/to/cc/bcc/subject/html/text/ header/priority/returnPath (mutable, chainable).
  • Address value object that parses and renders "Name <email>".
  • SendResult with ok()/fail()/cancelled() factories.
  • TransportInterface and the NullTransport implementation.
  • MailerManager (resolves the default and named mailers from Config\Mailer via an extensible transport map, lazily and cached) and a minimal Mailer that clones the message at the dispatch boundary.
  • Config\Mailer configuration and the service('mailer') service.
  • MessageRenderer that serialises an Email into a raw RFC 5322 / MIME string: text/plain, or multipart/alternative whenever HTML is present (with an automatically generated HTML→text fallback when no text body is set). Emits custom headers, Return-Path/Sender, and non-default X-Priority; applies RFC 2047 header encoding and quoted-printable body encoding (7-bit clean, within the 998-octet SMTP limit); strips CR/LF to prevent header injection; exposes the rendered header set via headers().
  • LogTransport and the built-in log mailer, which render the message and write the full MIME to a PSR-3 log channel (default level debug) instead of delivering it.
  • Email lifecycle events fired around every send: email.composing (at the start), email.sending (immediately before the transport — returning false cancels the send and yields SendResult::cancelled()), and email.sent / email.failed afterwards (each receiving the Email and SendResult). All emission is gated behind Config\Mailer::$fireEvents (default true).
  • SendmailTransport/MailTransport and the sendmail and mail mailers, which hand the rendered MIME to a local MTA: sendmail pipes it to the configured binary (path) with -oi -t, and mail splits it across PHP's native mail(). Both deliver Bcc via a header the MTA strips, set the envelope sender (-f) from the return path or From (validated and shell-escaped), and talk through the SendmailProcess/MailFunction seams for testing.
  • LegacyEmailAdapter, a drop-in replacement for CodeIgniter 4's email service. service('email') now returns the adapter, which exposes the full legacy fluent API (setFrom/setTo/setCC/setBCC/setSubject/setMessage/ setAltMessage/setMailType/setHeader/setPriority/setWordWrap/ setProtocol/attach/send/printDebugger/batchBCCSend/validation helpers) on top of the new Email builder and Mailer. Honors the legacy flat Config\Email keys (protocol, SMTP*, mailPath, mailType, wordWrap/wrapChars, priority, BCCBatch*, DSN) so existing apps need no configuration changes. Invalid addresses are swallowed (send() returns false and never throws). For CI4 parity, Return-Path and Reply-To default to From, and sends flow through the event pipeline (a email.sending listener can cancel). See Legacy Compatibility.
  • Optional hard word-wrap for the plain-text part: set Email::$wordWrap (and $wrapChars, default 76) and MessageRenderer wraps at word boundaries, leaving long space-less tokens (e.g. URLs) intact.
  • Mailer::fake() and the FakeTransport test double: swaps the bound service('mailer') for an in-memory recorder (no network, no real transport) and returns it. Exposes content-matcher assertions — assertSent(), assertSentTo(), assertNotSent(), assertNothingSent(), assertSentCount() — and sent() for inspecting the recorded messages. See Testing Mail.
  • Mailable, an abstract class-based email definition: compose the message in build() (which runs lazily at send time) via protected from/to/subject/ html/text helpers, pick a named mailer with transport(), and send() to route through service('mailer'). Each send tags the message with the Mailable's class. The make:mailable Spark command scaffolds a class into app/Mails/ (--force overwrites). FakeTransport::sent() and assertSent() now also accept a Mailable class-string (with an optional closure filter) so tests can assert by type. See Mailables.