I'm using Swift Mailer to send mail to a fairly large list of email addresses. The message is customized slightly for each recipient. Some placeholders are replaced with unique text.
I assumed it would be more efficient (less garbage collection etc) to reuse one instance of Swift_Message
multiple times. I just setTo()
, setBody()
and send again to the next address.
That works well with a single part body, usually just html. But ... when I want a second part, usually text, I can do addPart()
, but next time around the loop, that will add another part, and then another part etc ... The problem doesn't happen with setBody
because that overwrites the existing body.
Is there a way to overwrite or remove an existing part?
Thanks
According to the Swift Mailer Docs, there is no such method as a getBody()
really.
I would suggest you to store the actual message template containing your placeholders in a variable and use setBody()
on every iteration to replace the complete body of the message with the parsed template for the new recipient instead of trying to replace a tiny bit of it.
I reckon your code could look something like this:
$template = '<h1>Hello {{firstname}}</h1>, ...';
$recipients = [
['firstname' => 'John', 'email' => 'john@example.com'],
...
];
$message = Swift_Message::newInstance('Subject here');
foreach ($recipients as $recipient) {
$body = str_replace('{{firstname}}', $recipient['firstname'], $template);
$message->setBody($body, 'text/html');
// continue building the mail object and send it ...
}
This way you could indeed re-use the Swift_Message instance for every mail. Although it wouldn't make any difference to PHP if you re-use the instance or create a new one in every loop iteration.
@tetranz As @Hendrik outlines you can use $message->getBody()
and $message->setBody($body)
for the main body part.
You can access the parts (of the multipart addPart()
you mention) via the same methods of the children. i.e. something similar to:
$children = $message->getChildren();
foreach($children as &$child) {
$c_body = $child->getBody();
//manipulate as you wish
$child->setBody($c_body);
}
$message->setChildren($children);
This works on Swift v5.0.1
To expand on the answers by Hendrik and Alasdair, I suggest looking into using the Decorator plugin. http://swiftmailer.org/docs/plugins.html#using-the-decorator-plugin
Instead of manipulating the individual message parts, the plugin will replace the desired placeholders of the entire message for each recipient.
For example
$message = \Swift_Message::newInstance();
$replacements = array();
foreach ($users as $user) {
$replacements[$user['email']] = array(
'{username}' => $user['username'],
'{password}' => $user['password']
);
$message->addTo($user['email']);
}
$decorator = new \Swift_Plugins_DecoratorPlugin($replacements);
$mailer->registerPlugin($decorator);
$message
->setSubject('Important notice for {username}')
->setBody(
"Hello {username}, we have reset your password to {password}
" .
"Please log in and change it at your earliest convenience."
);
$message->addPart('{username} has been reset with the password: {password}', 'text/plain');
//..
$mailer->send($message);
Additionally in PHP, objects are passed by reference, so you can directly manipulate the individual parts body, or content type.
$textPart = \Swift_MimePart::newInstance('Hello World', 'text/plain');
$htmlPart = clone $textPart;
$htmlPart->setContentType('text/html');
$message->setTo('someone@example.com');
$message->attach($htmlPart);
$message->attach($textPart);
//...
$mailer->send($message);
$textPart->setBody('Something Else');
$htmlPart->setBody('Something Else');
$message->setTo('someone.else@example.com');
$mailer->send($message);
You can also remove a child part by using
$message->detach($textPart);
Instead of using detach, which iterates over the parts, looking at how addPart
and attach
work, they simply call setChildren(array_merge($this->getChildren(), array($part)))
So you can set the children parts manually by defining them, which replaces calling addPart
or attach
.
$message->setChildren([$htmlPart, $textPart]);
For all intents and purposes, if you're removing parts of a Message along with different content (albeit slightly) for another recipient, you're in effect creating a new Message. The programming logic can reflect that by calling $message = \Swift_Message::newInstance()
when the message parts need to be replaced.