1Plugins
2=======
3
4Plugins exist to extend, or modify the behaviour of Swift Mailer. They respond
5to Events that are fired within the Transports during sending.
6
7There are a number of Plugins provided as part of the base Swift Mailer package
8and they all follow a common interface to respond to Events fired within the
9library. Interfaces are provided to "listen" to each type of Event fired and to
10act as desired when a listened-to Event occurs.
11
12Although several plugins are provided with Swift Mailer out-of-the-box, the
13Events system has been specifically designed to make it easy for experienced
14object-oriented developers to write their own plugins in order to achieve
15goals that may not be possible with the base library.
16
17AntiFlood Plugin
18----------------
19
20Many SMTP servers have limits on the number of messages that may be sent during
21any single SMTP connection. The AntiFlood plugin provides a way to stay within
22this limit while still managing a large number of emails.
23
24A typical limit for a single connection is 100 emails. If the server you
25connect to imposes such a limit, it expects you to disconnect after that number
26of emails has been sent. You could manage this manually within a loop, but the
27AntiFlood plugin provides the necessary wrapper code so that you don't need to
28worry about this logic.
29
30Regardless of limits imposed by the server, it's usually a good idea to be
31conservative with the resources of the SMTP server. Sending will become
32sluggish if the server is being over-used so using the AntiFlood plugin will
33not be a bad idea even if no limits exist.
34
35The AntiFlood plugin's logic is basically to disconnect and the immediately
36re-connect with the SMTP server every X number of emails sent, where X is a
37number you specify to the plugin.
38
39You can also specify a time period in seconds that Swift Mailer should pause
40for between the disconnect/re-connect process. It's a good idea to pause for a
41short time (say 30 seconds every 100 emails) simply to give the SMTP server a
42chance to process its queue and recover some resources.
43
44Using the AntiFlood Plugin
45~~~~~~~~~~~~~~~~~~~~~~~~~~
46
47The AntiFlood Plugin -- like all plugins -- is added with the Mailer class's
48``registerPlugin()`` method. It takes two constructor parameters: the number of
49emails to pause after, and optionally the number of seconds to pause for.
50
51When Swift Mailer sends messages it will count the number of messages that have
52been sent since the last re-connect. Once the number hits your specified
53threshold it will disconnect and re-connect, optionally pausing for a specified
54amount of time::
55
56    // Create the Mailer using any Transport
57    $mailer = new Swift_Mailer(
58      new Swift_SmtpTransport('smtp.example.org', 25)
59    );
60
61    // Use AntiFlood to re-connect after 100 emails
62    $mailer->registerPlugin(new Swift_Plugins_AntiFloodPlugin(100));
63
64    // And specify a time in seconds to pause for (30 secs)
65    $mailer->registerPlugin(new Swift_Plugins_AntiFloodPlugin(100, 30));
66
67    // Continue sending as normal
68    for ($lotsOfRecipients as $recipient) {
69      ...
70
71      $mailer->send( ... );
72    }
73
74Throttler Plugin
75----------------
76
77If your SMTP server has restrictions in place to limit the rate at which you
78send emails, then your code will need to be aware of this rate-limiting. The
79Throttler plugin makes Swift Mailer run at a rate-limited speed.
80
81Many shared hosts don't open their SMTP servers as a free-for-all. Usually they
82have policies in place (probably to discourage spammers) that only allow you to
83send a fixed number of emails per-hour/day.
84
85The Throttler plugin supports two modes of rate-limiting and with each, you
86will need to do that math to figure out the values you want. The plugin can
87limit based on the number of emails per minute, or the number of
88bytes-transferred per-minute.
89
90Using the Throttler Plugin
91~~~~~~~~~~~~~~~~~~~~~~~~~~
92
93The Throttler Plugin -- like all plugins -- is added with the Mailer class'
94``registerPlugin()`` method. It has two required constructor parameters that
95tell it how to do its rate-limiting.
96
97When Swift Mailer sends messages it will keep track of the rate at which
98sending messages is occurring. If it realises that sending is happening too
99fast, it will cause your program to ``sleep()`` for enough time to average out
100the rate::
101
102    // Create the Mailer using any Transport
103    $mailer = new Swift_Mailer(
104      new Swift_SmtpTransport('smtp.example.org', 25)
105    );
106
107    // Rate limit to 100 emails per-minute
108    $mailer->registerPlugin(new Swift_Plugins_ThrottlerPlugin(
109      100, Swift_Plugins_ThrottlerPlugin::MESSAGES_PER_MINUTE
110    ));
111
112    // Rate limit to 10MB per-minute
113    $mailer->registerPlugin(new Swift_Plugins_ThrottlerPlugin(
114      1024 * 1024 * 10, Swift_Plugins_ThrottlerPlugin::BYTES_PER_MINUTE
115    ));
116
117    // Continue sending as normal
118    for ($lotsOfRecipients as $recipient) {
119      ...
120
121      $mailer->send( ... );
122    }
123
124Logger Plugin
125-------------
126
127The Logger plugins helps with debugging during the process of sending. It can
128help to identify why an SMTP server is rejecting addresses, or any other
129hard-to-find problems that may arise.
130
131The Logger plugin comes in two parts. There's the plugin itself, along with one
132of a number of possible Loggers that you may choose to use. For example, the
133logger may output messages directly in realtime, or it may capture messages in
134an array.
135
136One other notable feature is the way in which the Logger plugin changes
137Exception messages. If Exceptions are being thrown but the error message does
138not provide conclusive information as to the source of the problem (such as an
139ambiguous SMTP error) the Logger plugin includes the entire SMTP transcript in
140the error message so that debugging becomes a simpler task.
141
142There are a few available Loggers included with Swift Mailer, but writing your
143own implementation is incredibly simple and is achieved by creating a short
144class that implements the ``Swift_Plugins_Logger`` interface.
145
146* ``Swift_Plugins_Loggers_ArrayLogger``: Keeps a collection of log messages
147  inside an array. The array content can be cleared or dumped out to the screen.
148
149* ``Swift_Plugins_Loggers_EchoLogger``: Prints output to the screen in
150  realtime. Handy for very rudimentary debug output.
151
152Using the Logger Plugin
153~~~~~~~~~~~~~~~~~~~~~~~
154
155The Logger Plugin -- like all plugins -- is added with the Mailer class'
156``registerPlugin()`` method. It accepts an instance of ``Swift_Plugins_Logger``
157in its constructor.
158
159When Swift Mailer sends messages it will keep a log of all the interactions
160with the underlying Transport being used. Depending upon the Logger that has
161been used the behaviour will differ, but all implementations offer a way to get
162the contents of the log::
163
164    // Create the Mailer using any Transport
165    $mailer = new Swift_Mailer(
166     new Swift_SmtpTransport('smtp.example.org', 25)
167    );
168
169    // To use the ArrayLogger
170    $logger = new Swift_Plugins_Loggers_ArrayLogger();
171    $mailer->registerPlugin(new Swift_Plugins_LoggerPlugin($logger));
172
173    // Or to use the Echo Logger
174    $logger = new Swift_Plugins_Loggers_EchoLogger();
175    $mailer->registerPlugin(new Swift_Plugins_LoggerPlugin($logger));
176
177    // Continue sending as normal
178    for ($lotsOfRecipients as $recipient) {
179     ...
180
181     $mailer->send( ... );
182    }
183
184    // Dump the log contents
185    // NOTE: The EchoLogger dumps in realtime so dump() does nothing for it
186    echo $logger->dump();
187
188Decorator Plugin
189----------------
190
191Often there's a need to send the same message to multiple recipients, but with
192tiny variations such as the recipient's name being used inside the message
193body. The Decorator plugin aims to provide a solution for allowing these small
194differences.
195
196The decorator plugin works by intercepting the sending process of Swift Mailer,
197reading the email address in the To: field and then looking up a set of
198replacements for a template.
199
200While the use of this plugin is simple, it is probably the most commonly
201misunderstood plugin due to the way in which it works. The typical mistake
202users make is to try registering the plugin multiple times (once for each
203recipient) -- inside a loop for example. This is incorrect.
204
205The Decorator plugin should be registered just once, but containing the list of
206all recipients prior to sending. It will use this list of recipients to find
207the required replacements during sending.
208
209Using the Decorator Plugin
210~~~~~~~~~~~~~~~~~~~~~~~~~~
211
212To use the Decorator plugin, simply create an associative array of replacements
213based on email addresses and then use the mailer's ``registerPlugin()`` method
214to add the plugin.
215
216First create an associative array of replacements based on the email addresses
217you'll be sending the message to.
218
219.. note::
220
221    The replacements array becomes a 2-dimensional array whose keys are the
222    email addresses and whose values are an associative array of replacements
223    for that email address. The curly braces used in this example can be any
224    type of syntax you choose, provided they match the placeholders in your
225    email template::
226
227        $replacements = [];
228        foreach ($users as $user) {
229          $replacements[$user['email']] = [
230            '{username}'=>$user['username'],
231            '{resetcode}'=>$user['resetcode']
232          ];
233        }
234
235Now create an instance of the Decorator plugin using this array of replacements
236and then register it with the Mailer. Do this only once!
237
238::
239
240    $decorator = new Swift_Plugins_DecoratorPlugin($replacements);
241
242    $mailer->registerPlugin($decorator);
243
244When you create your message, replace elements in the body (and/or the subject
245line) with your placeholders::
246
247    $message = (new Swift_Message())
248      ->setSubject('Important notice for {username}')
249      ->setBody(
250        "Hello {username}, you requested to reset your password.\n" .
251        "Please visit https://example.com/pwreset and use the reset code {resetcode} to set a new password."
252      )
253      ;
254
255    foreach ($users as $user) {
256      $message->addTo($user['email']);
257    }
258
259When you send this message to each of your recipients listed in your
260``$replacements`` array they will receive a message customized for just
261themselves. For example, the message used above when received may appear like
262this to one user:
263
264.. code-block:: text
265
266    Subject: Important notice for smilingsunshine2009
267
268    Hello smilingsunshine2009, you requested to reset your password.
269    Please visit https://example.com/pwreset and use the reset code 183457 to set a new password.
270
271While another use may receive the message as:
272
273.. code-block:: text
274
275    Subject: Important notice for billy-bo-bob
276
277    Hello billy-bo-bob, you requested to reset your password.
278    Please visit https://example.com/pwreset and use the reset code 539127 to set a new password.
279
280While the decorator plugin provides a means to solve this problem, there are
281various ways you could tackle this problem without the need for a plugin. We're
282trying to come up with a better way ourselves and while we have several
283(obvious) ideas we don't quite have the perfect solution to go ahead and
284implement it. Watch this space.
285
286Providing Your Own Replacements Lookup for the Decorator
287~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
288
289Filling an array with replacements may not be the best solution for providing
290replacement information to the decorator. If you have a more elegant algorithm
291that performs replacement lookups on-the-fly you may provide your own
292implementation.
293
294Providing your own replacements lookup implementation for the Decorator is
295simply a matter of passing an instance of
296``Swift_Plugins_Decorator_Replacements`` to the decorator plugin's constructor,
297rather than passing in an array.
298
299The Replacements interface is very simple to implement since it has just one
300method: ``getReplacementsFor($address)``.
301
302Imagine you want to look up replacements from a database on-the-fly, you might
303provide an implementation that does this. You need to create a small class::
304
305    class DbReplacements implements Swift_Plugins_Decorator_Replacements {
306      public function getReplacementsFor($address) {
307        global $db; // Your PDO instance with a connection to your database
308        $query = $db->prepare(
309          "SELECT * FROM `users` WHERE `email` = ?"
310        );
311
312        $query->execute([$address]);
313
314        if ($row = $query->fetch(PDO::FETCH_ASSOC)) {
315          return [
316            '{username}'=>$row['username'],
317            '{resetcode}'=>$row['resetcode']
318          ];
319        }
320      }
321    }
322
323Now all you need to do is pass an instance of your class into the Decorator
324plugin's constructor instead of passing an array::
325
326    $decorator = new Swift_Plugins_DecoratorPlugin(new DbReplacements());
327
328    $mailer->registerPlugin($decorator);
329
330For each message sent, the plugin will call your class'
331``getReplacementsFor()`` method to find the array of replacements it needs.
332
333.. note::
334
335    If your lookup algorithm is case sensitive, you should transform the
336    ``$address`` argument as appropriate -- for example by passing it through
337    ``strtolower()``.
338