Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Mail Builder Laravel Package

dophp/laravel-mail-builder

dophp/laravel-mail-builder provides a fluent, code-driven way to compose rich HTML emails in Laravel, helping you build and send templated messages without hand-writing markup. A lightweight helper for cleaner mailables.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish.

composer require getodden/mail
php artisan vendor:publish --tag=mail-builder-assets

If you use the Filament field, publish through Filament instead so the field's assets land in the right place:

php artisan filament:assets

After every upgrade, re-publish. v2.4.0 changed the editor's module graph: odden-mail-editor.js now imports resources/js/editor/icons.js. An upgrade that updates the package but not your published assets leaves you with a half-updated editor. Use --force on the assets tag (or re-run filament:assets), then hard-refresh the browser. This still applies on v2.5.3 — re-publishing is cheap and removes asset-version skew as a variable when you debug anything else.

Where to look first.

  1. Dump the schema export and read it. It is the contract between the package and the editor, and as of v2.4.0 (schema version 3) it now carries groups (key, label per entry) plus short_label, group and description on every slot. Everything the new palette does — headings, tile labels, search, tooltips — is derivable from it.
  2. Read resources/js/editor/icons.js in the published output. It is inlined Heroicons v2 (MIT, no new dependency) and tells you exactly which icon names exist if you are mapping or overriding icons. The Filament block forms draw from the same icon set, so this is also your reference for form-level icon overrides.
  3. Grep your codebase for embedCidImages and CidImageEmbedder. v2.5.3 is a security hardening release that only changes behaviour for call sites that pass a base path, so this grep tells you in ten seconds whether the release affects you at all — and which templates to re-test if it does.
  4. Then open the editor once with a real template and confirm both tabs (Blocks / Structure) render before writing any integration code.

First use case. Build one real transactional email end to end — an order receipt is a good pick because order_receipt now has its own icon and the Commerce group gives you the tiles you need without scrolling. Include a logo image embedded through a base path so you exercise the v2.5.3 path rules as part of setup, author it in the editor, save the document, compile, and send it through a Mailable. That single loop exercises storage, compile, image embedding and delivery, which are the four places upgrades actually bite.

Upgrading from 2.4.x. If you are jumping straight to v2.5.3, note that the 2.5.0–2.5.2 releases are not reflected in this assessment — skim those notes too. From 2.4.0 to 2.5.3 specifically: no document migration, no schema change, no editor API change. The only behaviour change is the CID image base-path hardening described below.

Implementation Patterns

Decide where compilation happens, once. v2.4.0 explicitly does not change stored documents or compiled email, which means the two are independent layers you can treat differently: store the document (the editable source of truth), and compile either at send time or on save. Compile-on-save plus a cached HTML column is the pattern that scales — the editor becomes an authoring surface only, and your send path never touches the builder. Key the cache on the document hash so an editor save invalidates it automatically.

Embed images through a base path you control. Since v2.5.3, MailBuilder::embedCidImages() and CidImageEmbedder::embed() only embed a local image when the resolved path stays inside the base path you pass, is a regular readable file of at most 5 MB, and has an image MIME type. Treat that as the supported contract rather than a restriction to work around:

  • Keep every embeddable image under one dedicated directory (e.g. resources/mail-assets/) and pass that directory as the base path. Do not pass your project root — it technically works but turns any future template import into a file-read surface again.
  • Resolve paths to absolute before handing them over, and don't mix "sometimes relative, sometimes absolute" src conventions in the same template set.
  • Do the embedding at compile time, alongside compilation, and cache the result. Send-time embedding re-reads files from disk on every message and makes the 5 MB check a per-send failure mode.
  • Never pass a base path for HTML you did not author (user-supplied or imported templates). The hardening reduces what a ../secret src can reach; it does not make arbitrary HTML safe.

Filament as the authoring surface. Put the builder field in a resource form and let Filament own asset publishing. After every package upgrade, run php artisan filament:assets as part of your deploy, not as a manual step — mixed asset versions between environments is the most common "works locally, broken in staging" report with this package. Remember the block forms inside Filament rendered the new per-block icons too, so any icon override you apply must be applied where Filament reads it, not only in the standalone editor.

Build your own palette from the schema export. If the stock palette does not fit your team (too many Commerce blocks, say), consume the export instead of forking the editor:

  • Render headings from the top-level groups list, ordered as given.
  • Bucket slots by group key, not by label — labels are display strings and will change or be localized.
  • Use short_label for tile text, name + description for the tooltip and accessible name.
  • Always implement the fallback path: <odden-mail-editor> still accepts a schema with none of the new keys (one flat list, no headings, full labels, fallback icon), and your own palette should degrade the same way so an older export does not blank the panel.

Embedding the web component. Pass the schema to <odden-mail-editor> as JSON and treat the component as a controlled input — read the document back on change and persist it yourself. Do not reach into its internals to read state. Insertion semantics are unchanged in v2.4.0: clicking a tile inserts after the currently selected block, dragging drops it at the target position. If you wrap the editor in an undo stack or a change-log, key your entries on the emitted document, not on which UI affordance (click vs. drag) produced it.

Testing strategy that survives upgrades. Keep three distinct layers now:

  • Golden-file tests on compiled HTML. These should be untouched by editor releases (any diff there is a real regression) — but they will move if your templates embed images, because a rejected embed changes the emitted src. Keep an image-embedding fixture separate from the pure-markup fixtures so a hardening change doesn't churn your whole golden set.
  • Snapshot tests on the schema export. These will change on editor releases (v2.4.0 bumped them to version 3). Assert on the export's version marker before parsing so a future bump fails loudly instead of silently rendering a degraded palette.
  • Unit tests on the embedder boundary: one image inside the base path, one outside via ../, one over 5 MB, one with a non-image MIME, one protocol-relative URL. Five cases lock the v2.5.3 contract in place.

Multi-locale. groups[].label is a convenience string from the package, not your translation source. Map group.key to your own translation keys and render your labels, keeping the package label only as a fallback. Same for short_label vs name: tiles are space-constrained, so if you localize, check the longest of your translations against the two-column tile width.

Gotchas and Tips

v2.5.3 is conditional — check whether you pass a base path. Calls without a base path (data-URI-only embedding) behave exactly as before. If you never pass one, the release is a no-op for you and the only upgrade action is the usual asset re-publish. If you do pass one, re-test every template that embeds a local image, because previously-working images can now be silently skipped.

A rejected image is not an error you'll notice. Images that fail the base-path, size or MIME check are not embedded. Verify what your version emits in that case — most likely the original relative src survives into the compiled HTML, which renders as a broken image in the recipient's client rather than throwing. Add an assertion or a compile-time warning for "local image expected, none embedded" instead of relying on someone eyeballing a test send.

Symlinked asset directories are the most likely false negative. The check resolves the path and requires it to land inside the base path. A public/storage symlink into storage/app/public, or any deploy that symlinks assets into public/, resolves outside public/ and will be refused if public/ is your base path. Test one symlinked image explicitly; if it fails, point the base path at the real directory rather than loosening anything.

Stream wrappers no longer count as local paths. php://, file:// and similar are never treated as local, and neither are protocol-relative URLs (//cdn.example.com/logo.png). This is the intended fix, but it breaks two things people actually do: test fixtures using vfs:// or file:// paths, and templates that reference a CDN without a scheme. Use real files under the base path in fixtures, and write https:// explicitly on CDN images.

The 5 MB cap is per image, not per email. A newsletter with a dozen 4 MB images still passes each check and still produces a mail nobody's client will render happily. Treat 5 MB as a backstop, not a budget — set your own limit well below it and downscale on upload.

MIME is checked, not the extension. An image that is really a renamed file, or a format with patchy mail-client support, will be judged on its actual content type. Decide deliberately whether SVG belongs in your mail assets at all; it passes an image MIME check but is blocked by most clients.

--force overwrites local edits. Anything you changed inside the published resources/js/editor/ directory is gone. If you must customize, keep the customisation in your own layer (your own palette, your own CSS, your own icon map) and treat the published directory as read-only build output.

A missing icons.js breaks the whole editor, not just the icons. Because odden-mail-editor.js imports it, a 404 on that module fails module resolution and the panel renders blank. If self-hosting, keep icons.js in the same directory as odden-mail-editor.js and preserve the relative path — do not flatten or hash-rename it in a Vite/CDN pipeline. Symptom to recognise: empty editor plus a console module-resolution or 404 error, with the network tab showing icons.js missing.

The DOM changed even though the data did not. Any custom CSS or JS scoped to the old flat, scrollable block list — row selectors, .block-list > li style rules, scroll-position assertions in browser tests — is now targeting a tabbed, searchable, two-column tile grid. Scope custom styles to stable hooks you control (a wrapper element) rather than the palette's internal markup, and re-check visual regression tests as part of the upgrade.

Four icon keys changed. three_column → squares-plus, order_receipt → receipt-percent, dynamic_feed → arrow-path, labeled_divider → ellipsis-horizontal. Stored documents are unaffected, but any icon override map you maintain keyed on the old shared icon name will silently fall through to the default for exactly those four blocks — and the same map feeds the Filament block forms. Audit it against icons.js after the upgrade.

Search is per-word AND across fields. Every word must match somewhere in the block's name, short name, description or group — fields are combined, not matched individually. That is why a term that only appears in a description still returns a hit and why results can feel broader than expected. It also means a query can match nothing simply because one word is absent, so don't build "no blocks of this group exist" logic on top of an empty search result; the search filters, it does not answer questions about the schema.

Treat the new schema keys as optional on read. v2.4.0 is additive and backward compatible, but strict consumers are not: JSON Schema validators, generated TypeScript types and hand-written parsers that reject unknown or missing keys will break on either side of the change. Make groups, short_label, group and description optional in your types and default them, and version-gate: if the export reports version 3, use the grouping; otherwise fall back to a flat list.

Nothing to migrate. No document migration, no recompile, no email snapshot churn, no backfill. If your v2.5.3 upgrade plan includes a "re-render all templates" or "re-embed all images" step, you have misread the release — remove it. Re-test the image templates, don't rewrite them.

Accessibility is load-bearing, not decorative. The tabs follow the tabs pattern with arrow-key navigation, and full block names plus descriptions live in the tooltip and the accessible name because the tiles only show short_label. Copying tile markup into your own palette without carrying the accessible name and tooltip ships an editor that is unusable by keyboard and screen-reader users — and the two-column grid makes the labels shorter than they used to be, so this is easier to get wrong than before.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo