Problem/Motivation

Updated for Jan 2025: most of the important work has now been completed.

The main remaining work is to add some plugins that use the API.

Proposed resolution

Here are some ideas for how we might use attachments:

  1. Top priority: #3284140: Email adjuster to attach/embed by scanning the email body
  2. Field formatter that adds a file as an attachment, for example for simplenews newsletter
  3. Email adjuster to attach a file which could be from a URL or an existing Drupal managed file or a UI ("browse" button) to upload a new file
  4. Allow embedding images in BodyEmailAdjuster (possibly supporting the Twig syntax provided by Symfony)

Original issue

Symfony mailer supports embedding images and file attachements (https://symfony.com/doc/5.4/mailer.html#embedding-images / https://symfony.com/doc/5.4/mailer.html#file-attachments). At the moment this has not yet been implemented (as noted in https://www.drupal.org/docs/contributed-modules/symfony-mailer-0/feature...), and the related methods are commented in Drupal\symfony_mailer\BaseEmailTrait.

The OP requested to implement direct support for the symfony mailer functions below. As maintainer, I propose that we start from the requirement discussion above and see what API it leads to.

  • attach()
  • attachFromPath()
  • embed()
  • embedFromPath()
  • attachPart()
  • getAttachments()

Remaining tasks

User interface changes

API changes

Data model changes

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

hchonov created an issue. See original summary.

hchonov’s picture

StatusFileSize
new3.45 KB

I've tested it and it works sending attachemnts through the symfony mail methods.

adamps’s picture

Status: Needs review » Closed (duplicate)
Related issues: +#3261693: Implement embedded images

Thanks. It's a good start.

However, these are commented out for a reason, because it's a complex topic that needs some planning. The hardest part is security - we must not allow a user to email themselves a copy of protected files such as settings.php.

Another issue #3261693: Implement embedded images was raise a few hours before yours, and it has more information in the summary, so I'll close this one as a duplicate. You are welcome to add your patch there with a status of "Needs work" as it would likely help some people.

hchonov’s picture

I am confused why the issue with the patch is being closed. Isn't it easier to copy the information from the other issue? Also please note the other issue mentions everything that has been implemented here already....

seanb’s picture

Status: Closed (duplicate) » Needs review

Agreed, let's use this issue. I'll close mine as a duplicate.

adamps’s picture

Status: Needs review » Postponed
Issue tags: +Needs issue summary update

Sure, but in that case please copy in the IS from the other issue rather than only saying that it would be easier to do so (especially it had some good links). Also I now need to repost my comment from the other issue to here and correct the status here to "needs work". So whilst you saved yourself the (tiny) effort of reposting your patch, you made more work for the maintainer😃.

Comment from other issue...

This cannot be committed until the security is done, and that's quite hard. I would also like to test out some different use cases (see "features and status" linked from the IS for some ideas) to confirm the interface is right before commit. In fact I would even say we should have tests for these different use cases - which means we would need to create the testing framework first.

In my mind this is not the top priority for me to spend time on as it seems quite specialised/complex and many of the basics are also still not present. Anyone who is keen to use it earlier can apply the patch.

seanb’s picture

Issue summary: View changes

Updated the IS.

seanb’s picture

Issue summary: View changes
Status: Postponed » Needs work
attisan’s picture

So, after applying the patch - there still is no way to attach attachments to mails as the methods needed are not exposed within \Drupal\symfony_mailer\Email.php (yet? due to security concerns?)?

Mind if we create an issue fork so we can collaborate on getting this part working?

I'm currently trying to get attachments working (due to the demand for commerce_invoice to actually send invoices).

hchonov’s picture

So, after applying the patch - there still is no way to attach attachments to mails as the methods needed are not exposed within \Drupal\symfony_mailer\Email.php (yet? due to security concerns?)?

The patch makes the methods public so you can use it to send attachments, however further work is needed to enhance security.

attisan’s picture

So how could I use them - sorry for asking. I (until know) planned on creating \Drupal\commerce_invoice\Plugin\EmailBuilder\CommerceInvoiceEmailBuilder wich would handle adding the attachments from params. But having the \Drupal\symfony_mailer\Email (using the preRender method) I can't access the inner SymfonyEmail object ..?

guylyons’s picture

I'm also curious how to get an attachment working with this patch, and will the work with the BC module enabled? If someone would kindly describe their process, it would be greatly appreciated. Thanks.

rang501’s picture

StatusFileSize
new3.61 KB

Updated patch to fix fatal error because DataPart include was missing

sander wemagine’s picture

This is an updated patch from #13, this patch will sends attachments from the $params['attachments'] key in mails.

With this patch the Commerce Invoice attachments are being send on my installation. Make sure you enabled the sub module "Symfony Mailer Back-compatibility".

adamps’s picture

Thanks @Sander Wemagine. Patch #14 is very useful because it gives an example of how the API could be used. It would be great if other could upload examples too.

We don't necessarily want to just cope the symfony API - we should make things more "Drupaly". For example a common case will be to attach a Drupal File class, and the attached patch shows an interface for that. We might also want one based on the Drupal Url class. Perhaps we wouldn't so much use attachPart().

I would be interested to understand how the embed methods should be used. I am imagining the case of an embedded image - in which case we need to specify where in the email body the image should be displayed. The body render array could contain a specific render element to generate an embedded image.

attisan’s picture

Created a patch (MR) incorporating @AdamPSs ideas regarding some safeguards against being able to attach system files and added a bit of backward compatibility (instead of relying on modules to implement whatever symfony_mailer provides, symfony_mailer should try to provide means to "get going" - else integrators might just opt to use swiftmailer as "things just work").

My patch will not magically make attachments work in any situation. It is but a starting point.

attisan’s picture

Status: Needs work » Needs review

For the time being, and as stated - to have a starting point - I opted to include basic attachFromPath only as that is what modules I know do use; an array of paths to files within the attachment property of the mail params.

nojj’s picture

MR works for me

seanb’s picture

Here is an example of how we use this to add inline images to mail. Might be good to add something like this to the module in a followup.

<?php

namespace Drupal\mymodule\Plugin\EmailAdjuster;

use Drupal\Component\Uuid\UuidInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\symfony_mailer\Processor\EmailAdjusterBase;
use Drupal\symfony_mailer\EmailInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Defines the Inline Images Email Adjuster.
 *
 * @EmailAdjuster(
 *   id = "email_inline_images",
 *   label = @Translation("Inline images"),
 *   description = @Translation("Converts images to inline images."),
 * )
 */
class InlineImagesEmailAdjuster extends EmailAdjusterBase implements ContainerFactoryPluginInterface {

  /**
   * The Uuid Service.
   *
   * @var \Drupal\Component\Uuid\UuidInterface
   */
  protected $uuidService;

  /**
   * The file system service.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  protected $fileSystem;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): self {
    $plugin = new static($configuration, $plugin_id, $plugin_definition);

    $uuid_service = $container->get('uuid');
    $plugin->setUuid($uuid_service);
    $file_system = $container->get('file_system');
    $plugin->setFileSystem($file_system);

    return $plugin;
  }

  /**
   * Sets the Uuid Service.
   *
   * @param \Drupal\Component\Uuid\UuidInterface $uuid_service
   *   The Uuid Service.
   *
   * @return $this
   */
  public function setUuid(UuidInterface $uuid_service): self {
    $this->uuidService = $uuid_service;
    return $this;
  }

  /**
   * Sets the file system service.
   *
   * @param \Drupal\Core\File\FileSystemInterface $file_system
   *   The file system service.
   *
   * @return $this
   */
  public function setFileSystem(FileSystemInterface $file_system): self {
    $this->fileSystem = $file_system;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function postRender(EmailInterface $email): void {
    if ($body = $email->getHtmlBody()) {
      $dom = new \DOMDocument();
      $dom->loadHTML($body);

      foreach ($dom->getElementsByTagName('img') as $img) {
        /** @var \DomNode $img */
        $uuid = $this->uuidService->generate();
        $email->embedFromPath($this->fileSystem->realpath($img->getAttribute('src')), 'image-' . $uuid);
        $img->setAttribute('src', 'cid:image-' . $uuid);
      }

      $body = $dom->saveHTML();
      $email->setHtmlBody($body);
    }
  }

}
attisan’s picture

@seanB should I add your code to the MR?

adamps’s picture

Title: Attachments support » [META] Attachments support
Issue summary: View changes

Thanks for continuing the good work here. I have updated the IS.

I made this issue a META because I believe it will be solved in multiple stages. Already there are many patches each trying to do different things. I suggest that each person could raise a new issue describing specifically what they are trying to do, and adding their patch.

  • #20 (nice thanks) = item 2 in the new proposed resolution of the IS.
  • #14 adds automatic legacy support based on $params['attachments']. I like it, but also I would have some concerns about security.
  • The MR looks like two things, both useful - I suggest a separate issue for each of them: some legacy support and some security. I feel the security might be better in the core mailer/email classes rather than relying on an adjuster.
attisan’s picture

Thanks for the feedback @AdamPS. I have the feeling there are many patches - all actually trying to do the same thing - get attachments working with symfony_mailer 😉.

When you suggest moving the security part into the core mailer/email classes, do you have a guideline on how you would like attachments to be handled in first place? Moving to a strictly "drupaly" way will leave many modules and implementations behind as it will strain developers time to make adjustments to accommodate for the changed way of handling emails.

jeroent’s picture

StatusFileSize
new10.97 KB

Created a patch of #22 in case someone wants to use it.

attisan’s picture

Though well hidden, MRs can easily be used as patches directly by using the plain diff link 😉 (with the added bonus to stay up to date when things change / get fixed)

plain diff link

jeroent’s picture

(with the added bonus to stay up to date when things change / get fixed)

Yeah, I don't like the idea that someone with bad intentions can update the MR and break my site.
That's why I always use a patch file, so I control which code is added.

adamps’s picture

Issue summary: View changes

I have the feeling there are many patches - all actually trying to do the same thing - get attachments working with symfony_mailer

Well I described 4 (now 5) distinct cases in the IS😃. #20 and #14 are quite different.

do you have a guideline on how you would like attachments to be handled in first place?

Not fully. My development here is based on wanting the help the community and also on supporting sites I develop for. I don't need attachments for my own sites, and I'm really busy so it's hard for me to find a lot of time to work on it. However each time someone creates a new patch it really helps develop the overall picture. It would really help if someone could create separate cases listed in the IS, and move patches here to the appropriate new issue. Also I think a patch for case 2 is really important to confirm what would be a good interface.

Moving to a strictly "drupaly" way will leave many modules and implementations behind as it will strain developers time to make adjustments to accommodate for the changed way of handling emails.

I understand. This module is trying to balance two different motivations:

  • BC with the existing interfaces helps sites do a minimal migration
  • Clean, simple, Drupaly code helps new developments and is good for the long term

If we can get the "clean, simple" case working first, then I'll feel confident we have the right interfaces. Afterwards probably we can maximise legacy support also.

attisan’s picture

Not fully. My development here is based on wanting the help the community and also on supporting sites I develop for.

so are we all.

It would really help if someone could create separate cases listed in the IS

That would really be something I would love to see done by "a" (😋) maintainer - hence my mentioned guideline. Having this done by the community / non-maintainers, I have the feeling that it could end up in more work than necessary when things are implemented in a way, that isn't to your liking. - I'll try anyway if you say so / want me to.

imclean’s picture

Coming in from a non-Symfony Mailer perspective, I'm trying to get my head around this specifics of this task.

To simplify things (for my benefit at least), I imagine there would be 2 ways to add attachments:

  1. From a URL or an existing Drupal managed file
  2. From file contents

There are 2 ways for these to be attached:

  1. As an attachment
  2. Inline (e.g. #20)

Beyond that, there are plenty of ways of getting the file to the email. Some of them would be outside the scope of this module.

  1. Directly in code
  2. Parsing a twig template
  3. Uploading a file to a file field
  4. Parsing a fully constructed email

We need to consider security to control attaching of protected files. For Drupal managed files we could perhaps automatically check if the recipient has access to the file.

To a certain extent this could be handled by the module attaching the file. Files can also be emailed on cron.

Is the above too simple? I'm trying to get a handle on what needs to be done conceptually before getting into the specifics of Symfony Mailer.

jeroent’s picture

#3281115: Support sending attachments using symfony_mailer got committed so if this issue is fixed, sending attachments using Webform will also work.

adamps’s picture

That would really be something I would love to see done by "a" (😋) maintainer - hence my mentioned guideline

OK good idea, but I won't have time until June

attisan’s picture

I feel the security might be better in the core mailer/email classes rather than relying on an adjuster.

@AdamPS I have reviewed your opinion on this and am inclined to disagree with you. Though it might seem like a nice thing to have security directly in the base class, it will (obviously quite often, as it seems attachments aren't a big thing) not be used a lot and hence would be "dead meat" plus adds complexity for maintenance when things change.

Leaving it as an adjuster gives the webpage builder more flexibility over where he wants to enable attachment support (coincidentally adding to the security footprint) and separates this specific topic from the base implementation. Further more, it "feels" more endemic to how other parts of the module have already been implemented (adding adjusters to specific policies as needed).

Having the base email class provide the methods to add / inline attachments won't bear more risk when implemented with or without the "security part" being handled externally as a potential villain, already having access to directly call the method on the email object has very basic system access - simply using fopen would be less hassle 😅.

attisan’s picture

@AdamPS - side note; I wholeheartedly feel your need to split this issue into smaller sub-issues BUT (😂, there always is a but) it makes development super slow and extra un-comfy (especially when trying to stick to the new-era patch system of creating forks and MRs introduced 2020) as there is no (obvious?) way to create a fork based on an another issues fork. So adding all those needed sub-parts, the fact that this module only has a single maintainer (you), mixed with your tight time schedule "kind of" chokes the currently development and leaves webpage builders either using different patches listed here (so bugs aren't found as fast due to spread usage) or stalls the transition from swfitmailer to symfony_mailer.

nojj’s picture

MR !22 does not apply together with MR!24 from here
https://www.drupal.org/project/symfony_mailer/issues/3280322
alone they both apply fine.

adamps’s picture

Issue summary: View changes
Status: Needs review » Active

I created 2 child issues and updated the IS with some comments about the likely interface IMO.

@attisan I don't understand any way to commit this whole issue, because as I already said there are different patches doing quite different things. However each of the child issues I created could be committed.

@imclean #29 roughly matches my thinking

nojj’s picture

since MR!22 does not apply to the latest DEV, I have a WSOD at admin/config/system/mailer and don?t know how to fix it.

Drupal\Component\Plugin\Exception\PluginNotFoundException: The "mailer_attachments" plugin does not exist. Valid plugin IDs for Drupal\symfony_mailer\Processor\EmailAdjusterManager are: mailer_url_to_absolute, email_bcc, email_body, email_cc, mailer_default_headers, email_from, mailer_html_to_text, mailer_inline_css, email_plain, email_priority, email_reply_to, email_skip_sending, email_subject, email_theme, email_to, email_transport in Drupal\Core\Plugin\DefaultPluginManager->doGetDefinition() (line 53 of core/lib/Drupal/Component/Plugin/Discovery/DiscoveryTrait.php).
Drupal\Core\Plugin\DefaultPluginManager->getDefinition('mailer_attachments') (Line: 16)
Drupal\Core\Plugin\Factory\ContainerFactory->createInstance('mailer_attachments', Array) (Line: 83)
Drupal\Component\Plugin\PluginManagerBase->createInstance('mailer_attachments', Array) (Line: 17)
Drupal\symfony_mailer\Processor\AdjusterPluginCollection->initializePlugin('mailer_attachments') (Line: 80)
Drupal\Component\Plugin\LazyPluginCollection->get('mailer_attachments') (Line: 24)
Drupal\symfony_mailer\Processor\AdjusterPluginCollection->sortHelper('mailer_attachments', 'mailer_default_headers')
uasort(Array, Array) (Line: 90)
Drupal\Core\Plugin\DefaultLazyPluginCollection->sort() (Line: 268)
Drupal\symfony_mailer\Entity\MailerPolicy->getSummary() (Line: 45)
Drupal\symfony_mailer\MailerPolicyListBuilder->buildRow(Object) (Line: 219)
Drupal\Core\Entity\EntityListBuilder->render() (Line: 23)
Drupal\Core\Entity\Controller\EntityListController->listing('mailer_policy')
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 564)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 158)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 80)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 58)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 106)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 85)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 265)
Drupal\shield\ShieldMiddleware->bypass(Object, 1, 1) (Line: 221)
Drupal\shield\ShieldMiddleware->handle(Object, 1, 1) (Line: 42)
Drupal\webform_product\RedirectMiddleware->handle(Object, 1, 1) (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 708)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
nojj’s picture

is anybody working on an updated patch vor attachments for commerce Invoice? as non of the previous patches/MR apply to the latest alpha.
Thanks in advance.

hchonov’s picture

Status: Active » Needs review
StatusFileSize
new8.75 KB

I've re-rolled #24 but couldn't see how to and if I have to reapply the only single reject as the code has changed too much:

 /**
  * Defines the Legacy Email Builder plug-in that calls hook_mail().
@@ -67,7 +79,7 @@ class LegacyEmailBuilder extends EmailBuilderBase implements ContainerFactoryPlu
       throw new SkipMailException('Send aborted by hook_mail().');
     }
 
-    $email = $factory->newModuleEmail($message['module'], $message['key']);
+    $email = $factory->newModuleEmail($message['module'], $message['key'], $message['params']);
     $this->mailManager->emailFromArray($email, $message);
     return $email;
   }
nojj’s picture

Thanks @hchonov !
Patch apples to alpha10 and strangely fixes issue described here:
https://www.drupal.org/project/symfony_mailer/issues/3292133

but it does no send pdf invoices from commerce invoice :-(
the "old" patch did...

hchonov’s picture

StatusFileSize
new9.67 KB
new759 bytes

I've added a webform email builder so that I can bring up the webform attachments from the legacy_message when using the BC module to the email parameters. Now you need only to create a Webform policy and add the attachments adjuster and you can send attachments through webforms. I am not sure if we should rather bring this to the LegacyEmailBuilder?

nojj’s picture

has anyone tested patch #38 and can send attachments with commerce invoice?
I don't get any error messages, but I also don't get the pdf attached.

nojj’s picture

Maybe this helps debugging:

with mailhog at the working installation (older dev version of Symfony Mailer and MR22) I get this at MIME

multipart/alternative; boundary=xjCKpyYW (1829 bytes)
 Download  application/pdf; name=20220686-de-paid.pdf (26916 bytes)

and with patch #40 I get

text/plain; charset=utf-8 (2020 bytes)
 Download  text/html; charset=utf-8 (33636 bytes)

I can't tell if this is problem with the patch or a setup problem.

adamps’s picture

Status: Needs review » Active

This is now a meta issue so please use other issues for patches:

adamps’s picture

jrockowitz’s picture

I am working on getting webforms with email attachments working for a client using the Symfony Mailer module. It took me a little while to figure out the exact steps required, and I wanted to share my notes.

Below are my steps to get Webform email attachments via SMTP.

  • Update to the latest dev release
  • Apply patch from #40
  • Enable Symfony Mailer and Symfony Mailer Back-compatibility modules (/admin/modules)
  • Configure Webform Mailer policy with attachments. (/admin/config/system/mailer)
  • Add SMTP transport (/admin/config/system/mailer/transport)
  • Add file to the default Contact webform and update emails to attached files (/form/contact)
nojj’s picture

thanks for the detailed explanation.

adamps’s picture

@jrockowitz Thanks for posting. Please can you recheck your results the latest dev release? Since #3284142: Add support to attachments for LegacyEmailBuilder was fixed as noted in #43, the patch from #40 should not be needed, and will no longer apply.

nchase’s picture

can confirm #47 - latest dev works. Attachment from commerce invoice get sent.

jungle’s picture

Status: Active » Needs review
StatusFileSize
new7.18 KB

Rerolled, no interdiff.

jungle’s picture

StatusFileSize
new603 bytes
new7.24 KB

Fix
Error: Class "Drupal\symfony_mailer_bc\Plugin\EmailBuilder\LegacyEmailBuilder" not found in include() (line 14 of /var/www/html/web/modules/contrib/symfony_mailer/modules/symfony_mailer_bc/src/Plugin/EmailBuilder/WebformEmailBuilder.php)

jungle’s picture

StatusFileSize
new9.18 KB
new1.94 KB

To get work the issue in comment 11 or 13 of #3310048: Support Symfony Mailer for attachments and deprecate Swiftmailer, if $attachment['filecontent'] exists, use it.

adamps’s picture

Category: Feature request » Plan
Status: Needs review » Active

Great thanks for the patch. This is a meta issue that will stay open until all the parts are done. Please create a new issue for any patch to be committed.

jungle’s picture

Ok, @AdamPS added one with what I encountered and linked the patch in #51 there. #3328847: Support attachments with 'filecontent'

Thanks!

nchase’s picture

#51 works. The attachments uploaded via webform get send via symfony mailer. Thank you!

3cwebdev’s picture

Yes! #51 did the trick and my Webform PDF attachments are now sending again. Thank you :)

adamps’s picture

NB patch #51 is being developed in #3328847: Support attachments with 'filecontent' and there is a new patch that needs review please. Please put any comments on the other issue.

1i1c’s picture

@jrockowitz
Thank you for detailed explanation. I tried your approach using MailHog and the Webform can send emails with attachments. However, it appears that sending email using sendmail still missing attachments. Any solution for webform without SMTP?

wouter waeytens’s picture

StatusFileSize
new1.1 KB

Update patch to work with the latest stable release.

wouter waeytens’s picture

StatusFileSize
new8.34 KB

Update patch to work with the latest stable release. Forgot the staged files.

adamps’s picture

This is a meta issue and its purpose is to group together related issues. Please create separate issues for each part of the work. Patches here will not be committed.

loopy1492’s picture

Well, in the mean-time, thanks for keeping the patches updated @Wouter Waeytens .

adamps’s picture

Issue summary: View changes
adamps’s picture

adamps changed the visibility of the branch 3261807-attachments-support to hidden.

adamps’s picture

Version: 1.x-dev » 2.x-dev

adamps’s picture

I created a branch for proposed resolution #3: Attach file adjuster. It is a work in progress: all you can do is attach an existing Drupal managed file, and there is no browser, so you need to type in the URL.