Search by

bredabeds / magento-email-attachments

bredabeds

Attach files to Magento emails, including the emails Magento sends itself (Magento 2.4.9+, Symfony Mailer)

Package info

github.com/BredaBeds/magento-email-attachments

Type:magento2-module

pkg:composer/bredabeds/magento-email-attachments

Statistics

Installs: 10

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

2.0.0 2026-10-29 18:31 UTC

This package is auto-updated.

Last update: 2026-10-29 18:33:38 UTC


README

Attach files to any email Magento sends, including the emails Magento sends itself (order, invoice and shipment confirmations), without replacing Magento's mail classes.

Supports Magento 2.4.9+ (Symfony Mailer, symfony/mime 7). Version 1.x built attachments with Laminas\Mime, which Magento 2.4.8 removed from the mail stack: on 2.4.8+ 1.x attachments no longer work.

Install

composer require bredabeds/magento-email-attachments:^2.0
bin/magento module:enable BredaBeds_EmailAttachments
bin/magento setup:upgrade
bin/magento setup:di:compile

The attachment directory

Attachments are regular files in var/email-attachments/. Any other path is refused: a template variable must never be able to mail out an arbitrary file of the installation. Write files with Storage::put(), which takes a plain relative path (letters, digits, ., _, -; sub-directories are created) and returns the absolute path to hand to the module:

public function __construct(private readonly \BredaBeds\EmailAttachments\Model\Storage $storage) {}

$path = $this->storage->put('invoices/invoice_100001.pdf', $pdfBytes);

The directory is a staging area, not an archive: a daily cron job (bredabeds_email_attachments_cleanup) deletes files older than 30 days. Keep your own copy if you need one.

Paths are checked on their real location: .., symlinks that point outside the directory, directories, missing files and stream wrappers (php://…) are all refused.

Attach from an observer (template variable)

Code that does not hold the transport builder, for example an observer of email_order_set_template_vars_before, adds the template variable Attachment::TEMPLATE_VAR (bredabeds_email_attachments). The module removes it from the variables before the template is rendered.

use BredaBeds\EmailAttachments\Model\Attachment;

public function execute(\Magento\Framework\Event\Observer $observer): void
{
    $transportObject = $observer->getData('transportObject');
    $vars = $transportObject->getData();
    $vars[Attachment::TEMPLATE_VAR] = [
        ['path' => $path, 'filename' => 'Invoice.pdf', 'type' => 'application/pdf'],
    ];
    $transportObject->setData($vars);
}

Three shapes are accepted:

$vars[Attachment::TEMPLATE_VAR] = $path;                                         // one path
$vars[Attachment::TEMPLATE_VAR] = ['path' => $path, 'filename' => 'A.pdf'];      // one file with options
$vars[Attachment::TEMPLATE_VAR] = [$path1, ['path' => $path2, 'type' => 'text/csv']];   // a list of either

filename defaults to the file's own name, type to the MIME type detected from the content.

The same key works with any TransportBuilder::setTemplateVars() call.

Attach from code that holds the transport

use BredaBeds\EmailAttachments\Model\Attacher;
use BredaBeds\EmailAttachments\Model\AttachmentFactory;

$transport = $this->transportBuilder
    ->setTemplateIdentifier('my_template')
    ->setTemplateOptions(['area' => 'frontend', 'store' => $storeId])
    ->setTemplateVars($vars)
    ->setFromByScope('general', $storeId)
    ->addTo($email)
    ->getTransport();
$this->attacher->attach($transport->getMessage(), $this->attachmentFactory->fromFile($path, 'Invoice.pdf'));
$transport->sendMessage();

Public API (@api): Model\Attachment, Model\AttachmentFactory, Model\Storage, Model\Attacher.

Limits and failures

  • At most 15 MiB of attachments per email (about 20.6 MB once base64-encoded), empty files are refused.
  • An invalid attachment (outside the directory, missing, empty, too large, bad MIME type, wrong shape) fails that email: getTransport() throws a LocalizedException. For Magento's sales emails the sender logs it and the email is not sent (queued emails are retried per sales_email/general/async_sending_attempts); other emails are unaffected. Nothing is skipped silently.
  • Error messages name the file or the type, never a path outside the directory or the content.

To verify, send a test email through an SMTP log (for example a mail-log module) or a mail catcher and check the attachment part.

Changelog

2.0.0

  • Magento 2.4.9+ only: attachments are Symfony Mime parts.
  • Plugins on TransportBuilder instead of a preference; the core builder is no longer replaced.
  • Attachments must live in var/email-attachments/ (Storage::put()); daily cleanup after 30 days.
  • Template variable renamed from attachments to bredabeds_email_attachments (Attachment::TEMPLATE_VAR).
  • TransportBuilder::addAttachment() replaced by Attacher::attach().
  • Removed: the Browserless PDF helper, the basic mailer helper and its simple_text_email template, the bredabeds:email-attachments:email-confirmation command.