getodden/mail
getodden/mail is a Laravel package that simplifies sending and managing email in PHP applications. It provides a clean, straightforward API for composing messages, configuring mail delivery, and integrating email workflows into your Laravel projects with less boilerplate.
Architecture fit — The editor-side contract is unchanged since v2.4.0, but v2.5.3 moves the package's risk surface somewhere new: the server-side CID embedding pipeline (MailBuilder::embedCidImages() / CidImageEmbedder::embed()). That is the first release we've assessed that touches the compile/render path rather than the admin panel, and it touches it at the point where HTML and the filesystem meet. Architecturally the fix is the right shape — validation lives in the package, not in our call sites — and it is opt-in-by-default only in the sense that behaviour changes only when a base path is passed. Calls that pass no base path (data-URI-only embedding) are untouched. That containment is good news for adoption but means our exposure is entirely determined by which of our call sites pass a base path.
Integration feasibility — Mechanically still composer update, but the version hop is 2.4.0 → 2.5.3, which means we are absorbing three releases we have not reviewed (any 2.5.0–2.5.2 changes, plus asset changes if any). Two concrete actions: read the full changelog across the gap, then audit call sites. Grep for embedCidImages( and CidImageEmbedder::embed( and classify each as base-path or no-base-path. For the base-path call sites, verify (a) the base path is the one we intend, (b) every legitimately referenced image resolves inside it, (c) no legitimate asset exceeds 5 MB, (d) MIME detection succeeds for the formats we actually ship (WebP, AVIF and SVG are the usual surprises), and (e) no stored template HTML relies on php://, file://, or protocol-relative src values that previously "worked". Only the last class is truly broken by design; the rest are configuration mismatches the fix will expose.
Technical risk — Two risks in opposite directions. First, not upgrading: the described vulnerability is a local file disclosure — a relative src such as ../secret in HTML we do not fully control could attach a file from outside the base directory into an outbound email. That is recipient-facing data exfiltration, and it is a stop-the-line issue if any of our templates accept author-supplied or imported HTML. Second, upgrading blindly: the fix fails closed and, per the notes, silently — an image that previously embedded and now does not simply arrives as a remote or broken reference. Unlike the v2.4.0 asset case, this failure is quiet and lands in the recipient's inbox, not in a browser console. Sub-risks: legitimate shared asset directories referenced via .. from the base path break; product photography over 5 MB stops inlining; symlink handling depends on what "resolves inside that base path" means in practice. No database change, no stored-document change, no migration; rollback is a version pin, but rolling back re-opens the disclosure.
Key questions — (1) What shipped in 2.5.0, 2.5.1 and 2.5.2, and did any of them change editor assets or the schema export version? (2) Is the 5 MB ceiling a constant or configurable, and is it per-image or per-message? (3) When an image is rejected, is there a log entry, a collected warning, or nothing at all — i.e. can we observe skips in staging, or must we diff compiled output? (4) Does "resolves inside the base path" follow symlinks (realpath) or compare literal paths, and does it reject the base path itself as a directory? (5) Is the MIME check content-sniffed or extension-derived, and what is the allowlist? (6) Is there a published advisory/CVE and an affected-version range, so we know whether every version we have ever deployed is vulnerable? (7) What is the vendor's semver position on shipping a behaviour change in a patch release — do we need to treat 2.5.x upgrades as minor-level change control going forward?
Stack fit — Unchanged: Laravel + Composer, Filament optional, no new runtime dependency and no Node step. The new work is plain PHP inside a service we already call, so it fits without touching our asset pipeline or our queue/worker topology. The one fit question is where our base path comes from — if it is derived from tenant or user input rather than a config value, that is worth tightening on our side regardless of what the package does.
Migration path — Small but non-trivial this time, because the gap spans multiple releases. Sequence: read the changelog for 2.4.x and 2.5.x; pin 2.5.3; composer update getodden/mail; audit and classify embedding call sites; build a regression fixture set; republish editor assets if the gap releases touched them (otherwise skip and avoid an unnecessary --force); clear caches; deploy to staging; send real template samples and confirm images arrive inline; then production. There is still no backfill and no dual-write window, but unlike v2.4.0 there is a genuine behavioural verification step before we can call it done.
Compatibility — Backward compatible for no-base-path callers, which is the majority of simple installs. For base-path callers the change is a tightening: previously-embedded inputs may now be skipped. That is the correct direction, but it is a behaviour change shipped as a patch, so our own change record should call it a minor. Our in-house readers are unaffected; nothing about the schema export or stored documents moves.
Sequencing — (1) Changelog review across the 2.4.0→2.5.3 gap; (2) call-site inventory and classification; (3) regression tests covering the matrix: in-base regular image (embeds), out-of-base path including ../ traversal (rejected), php://, file:// and protocol-relative URLs (never treated as local), oversized file (rejected), non-image MIME (rejected), and no-base-path call (unchanged); (4) republish assets and commit the public/ diff only if the gap releases changed them; (5) editor smoke test as per the v2.4.0 checklist — palette groups, search, tabs, drag-and-drop, the four re-iconed blocks; (6) staging send with image-heavy and image-light templates, comparing compiled MIME to a pre-upgrade baseline; (7) production, with the security note in the release record so nobody re-pins backwards for convenience.
Maintenance — The package is now on our security-relevant list, not just our admin-UX list: it reads files based on content it is handed. That means subscribing to its advisories and treating patch releases as mandatory rather than opportunistic. The v2.4.0 rule still stands — never edit published editor assets, treat the published set as generated — and the new equivalent is: never compensate for a rejected image by loosening the base path or raising limits without a recorded decision, because that is how the traversal hole gets reopened in application code.
Support — Two new support shapes. Upstream, if we do not upgrade, the exposure is a security incident rather than a ticket. Downstream, authors may report "images missing from sent emails" after the upgrade if any template referenced out-of-base paths, oversized files, or unusual formats. Because the skip is not loud, the first report may come from a recipient. Budget a short note to authors explaining that local images must live within the configured asset path and be under 5 MB, and that remote URLs are no longer fetched as local files.
Scaling — The validation adds a path resolution plus a stat and a read per candidate image, which is negligible against the cost of the embedding it guards. The real scaling note is memory: a 5 MB image inlines to roughly a 6.7 MB base64 string, so a template with several large images now has a defined per-image ceiling but no defined per-message ceiling — worth a cap on our side and a load test on our heaviest template. No change to the editor bundle from this release, so the v2.4.0 payload guidance stands; compiled output for compliant images is byte-identical, so deliverability and rendering at scale are unaffected.
Failure modes — Ranked: (1) unpatched disclosure — crafted relative src attaches a file from outside the base directory into outbound mail; recipient-visible, irreversible once sent, and the reason this release is mandatory. (2) silent CID skip — a legitimate image that fails one of the new checks ships as a broken or remote reference with no error surfaced; detect via a staging diff of compiled MIME, not via logs unless question 3 says otherwise. (3) base path misconfiguration — pointing the base path at the wrong root now rejects everything, turning a working template into an image-less one; caught only by an end-to-end send. (4) asset drift and missing icons.js — carried forward from v2.4.0 if the 2.5.x line republished editor files; same symptoms, same loud ESM import failure, same fix. (5) --force clobbering local patches — unchanged, policy-mitigated. (6) stale browser cache on published asset URLs — unchanged. Rollback is a pin plus republishing the prior asset set; note that rolling back below 2.5.3 restores the vulnerability, so rollback should be time-boxed and paired with an input-side mitigation.
Ramp-up — Developer effort moves from near-zero to roughly half a day: changelog read, call-site audit, regression fixtures, and one staging send. Content authors need no training for this release, but they do need one paragraph of guidance on where local images may live and how large they may be — and any author who has been embedding images from outside the asset directory has a template to fix, which is the point of the patch. Update our own docs where they describe the embedding path, and refresh the v2.4.0 screenshot pass only if the gap releases changed the editor panel.
How can I help you explore Laravel packages today?