-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathemails.rst
624 lines (469 loc) · 22.6 KB
/
emails.rst
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
Emails
######
There are multiple ways to extend the way Mautic works with Emails. This document describes the following options for extending Mautic's Email capabilities:
- Email tokens
- A/B testing
- Monitored Inbox Integration
- Email transport/Email providers
- Email stat helpers
.. Note:: Extending generally works by hooking into events using event listeners or subscribers. Read more about :doc:`listeners and subscribers</plugins/event_listeners>`.
Email tokens
------------
Email tokens are placeholders that you can insert into an Email.
They get replaced by Dynamic Content once the Email gets sent or viewed in the browser.
Email token capabilities consist of two parts: registering your custom tokens and rendering them.
Registering custom tokens in builders
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Registering tokens leverage the ``\Mautic\EmailBundle\EmailEvents::EMAIL_ON_BUILD`` event.
The event is dispatched before displaying the email builder form to allow adding of tokens.
An event listener receives the ``Mautic\EmailBundle\Event\EmailBuilderEvent``.
Use its ``$event->addToken($token, $htmlContent)`` to add your token.
.. Note:: You can either hard code your tokens textual description in ``$htmlContent`` or use a translatable string.
Rendering custom tokens
^^^^^^^^^^^^^^^^^^^^^^^
Registering tokens leverage the
- ``\Mautic\EmailBundle\EmailEvents::EMAIL_ON_SEND`` event when the email is sent or the
- ``\Mautic\EmailBundle\EmailEvents::EMAIL_ON_SEND`` event when the email is rendered for viewing in a browser, i.e., after the Lead clicked the ``{webview_url}`` link.
An event listener receives in both cases the ``Mautic\EmailBundle\Event\EmailSendEvent``.
Replacing a custom token is as easy as using the events ``$event->addToken($token, $contentToReplaceToken)``.
Example
^^^^^^^
.. code-block:: PHP
<?php
// plugins/HelloWorldBundle/EventListener/EmailSubscriber.php
class EmailSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
EmailEvents::EMAIL_ON_BUILD => ['onEmailBuild', 0],
EmailEvents::EMAIL_ON_SEND => ['onEmailGenerate', 0],
EmailEvents::EMAIL_ON_DISPLAY => ['onEmailGenerate', 0],
];
}
public function onEmailBuild(EmailBuilderEvent $event): void
{
$event->addToken('{my_custom_token}', 'My Custom Token');
}
public function onEmailGenerate(EmailSendEvent $event): void
{
$event->addToken('{my_custom_token}', 'Hello <b>World!</b>');
}
}
.. Note:: If you need more complex replacing you can use the event's ``$event->getContent()`` and ``$event->setContent()`` methods. See the example in the following section.
Email A/B testing
-----------------
While Mautic supports :xref:`A/B testing` out of the box, you might have more complex needs to determine A/B test winner criteria.
- ``$event->addAbTestWinnerCriteria()`` allows you to do exactly that. Using your custom logic, you can decide the winner of such criteria. An example is shown below.
- ``$event->setAbTestResults()`` is where you set the actual A/B test results. More details are in the code example below.
Example
^^^^^^^
.. code-block:: PHP
<?php
// plugins/HelloWorldBundle/EventListener/EmailSubscriber.php
declare(strict_types=1);
namespace MauticPlugin\HelloWorldBundle\EventListener;
use Mautic\CoreBundle\Helper\TemplatingHelper;
use Mautic\EmailBundle\EmailEvents;
use Mautic\EmailBundle\Event\EmailBuilderEvent;
use Mautic\EmailBundle\Event\EmailSendEvent;
use MauticPlugin\HelloWorldBundle\HelloWorldEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
final class EmailSubscriber implements EventSubscriberInterface
{
private TemplatingHelper $templating;
public function __construct(TemplatingHelper $templating)
{
$this->templating = $templating;
}
public static function getSubscribedEvents(): array
{
return [
EmailEvents::EMAIL_ON_BUILD => ['onEmailBuild', 0],
EmailEvents::EMAIL_ON_SEND => ['onEmailGenerate', 0],
EmailEvents::EMAIL_ON_DISPLAY => ['onEmailGenerate', 0],
];
}
/**
* Register the token and a custom A/B test winner
*/
public function onEmailBuild(EmailBuilderEvent $event): void
{
// Displays the token in the email builder, so that users can easily find it and add it to their emails
$event->addToken('helloworld.token', 'Hello world token');
// Add AB Test Winner Criteria
$event->addAbTestWinnerCriteria(
'helloworld.planetvisits',
[
// Label to group by
'group' => 'plugin.helloworld.header',
// Label for this specific a/b test winning criteria
'label' => 'plugin.helloworld.emailtokens.',
// Event that will be used to determine the winner
'event' => HelloWorldEvents::ON_DETERMINE_PLANET_VISIT_WINNER
]
);
}
/**
* Search and replace tokens with content
*/
public function onEmailGenerate(EmailSendEvent $event): void
{
// Get content
$content = $event->getContent();
// Search and replace tokens
$content = str_replace(
'{helloworld.token}',
$this->templating->getTemplating()->render('HelloWorldBundle:SubscribedEvents\EmailToken:token.html.php'),
$content
);
// Set updated content
$event->setContent($content);
}
}
.. code-block:: PHP
<?php
// plugins/HelloWorldBundle/EventListener/PlanetVisitSubscriber.php
declare(strict_types=1);
namespace MauticPlugin\HelloWorldBundle\EventListener;
use Mautic\CoreBundle\Event\DetermineWinnerEvent;
use MauticPlugin\HelloWorldBundle\HelloWorldEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
final class PlanetVisitSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
HelloWorldEvents::ON_DETERMINE_PLANET_VISIT_WINNER => ['onDeterminePlanetVisitWinner', 0],
];
}
public function onDeterminePlanetVisitWinner(DetermineWinnerEvent $event): void
{
$event->setAbTestResults([
'winners' => [],
'support' => [
'labels' => ['label1', 'label2'],
'data' => [
'label1' => [100,200],
'label2' => [200,300]
],
'step_width' => 10
],
'supportTemplate' => 'HelloWorldBundle:SubscribedEvents\AbTest:bargraph.html.php'
]);
}
}
.. code-block:: PHP
<?php
// plugins/HelloWorldBundle/Views/SubscribedEvents/AbTest/bargraph.html.php
declare(strict_types=1);
$support = $results['support'];
$label = 'My chart label';
$chart = new \Mautic\CoreBundle\Helper\Chart\BarChart($support['labels']);
if ($support['data']) {
foreach ($support['data'] as $datasetLabel => $values) {
$chart->setDataset($datasetLabel, $values);
}
}
?>
<div class="panel ovf-h bg-auto bg-light-xs abtest-bar-chart">
<div class="panel-body box-layout">
<div class="col-xs-8 va-m">
<h5 class="text-white dark-md fw-sb mb-xs">
<?php echo $label; ?>
</h5>
</div>
<div class="col-xs-4 va-t text-right">
<h3 class="text-white dark-sm"><span class="fa fa-bar-chart"></span></h3>
</div>
</div>
<?php echo $view->render(
'MauticCoreBundle:Helper:chart.html.php',
['chartData' => $chart->render(), 'chartType' => 'bar', 'chartHeight' => 300]
); ?>
</div>
.. vale off
Monitored Inbox Integration
---------------------------
.. vale on
Plugins have access to hook into the ``mautic:email:fetch`` command to fetch email from a specific inbox/folder and process the content of the message.
The Plugin also has access to inject specific search criteria for the processed messages.
To do this, the Plugin needs to add an event listener for three events:
1. ``EmailEvents::MONITORED_EMAIL_CONFIG`` This event is dispatched to inject the fields into Mautic's Configuration to configure the IMAP inbox and folder that should be monitored.
2. ``EmailEvents::EMAIL_PRE_FETCH`` This event is dispatched during the execution of the ``mautic:email:fetch`` command. It's used to inject search criteria for the messages desired.
3. ``EmailEvents::EMAIL_PARSE`` This event parses the messages fetched by the command.
.. code-block:: PHP
<?php
// plugins/HelloWorldBundle/EventListener/MonitoredInboxSubscriber.php
declare(strict_types=1);
namespace MauticPlugin\HelloWorldBundle\EventListener;
use Mautic\EmailBundle\EmailEvents;
use Mautic\EmailBundle\Event\MonitoredEmailEvent;
use Mautic\EmailBundle\Event\ParseEmailEvent;
use Mautic\EmailBundle\MonitoredEmail\Mailbox;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
final class MonitoredInboxSubscriber implements EventSubscriberInterface
{
private $bundle = 'HelloWorldBundle';
private $monitor = 'deep_space_emails';
static public function getSubscribedEvents(): array
{
return [
EmailEvents::MONITORED_EMAIL_CONFIG => ['onConfig', 0],
EmailEvents::EMAIL_PRE_FETCH => ['onPreFetch', 0],
EmailEvents::EMAIL_PARSE => ['onParse', 0],
];
}
/**
* Inject the IMAP folder settings into the Configuration
*/
public function onConfig(MonitoredEmailEvent $event): void
{
/**
* The first argument is something unique to recognize this plugin.
* The second argument should be something unique to identify this monitored inbox.
* The third argument is the label for this monitored inbox.
*/
$event->addFolder($this->bundle, $this->monitor, 'mautic.world.monitored_deep_space_emails');
}
/**
* Inject search criteria for which messages to fetch from the configured folder.
*/
public function onPreFetch(ParseEmailEvent $event): void
{
$event->setCriteriaRequest($this->bundle, $this->monitor, Mailbox::CRITERIA_UNSEEN. " " . Mailbox::CRITERIA_FROM ." aliens@andromeda");
}
/**
* Parse the messages
*/
public function onParse(ParseEmailEvent $event): void
{
if ($event->isApplicable($this->bundle, $this->monitor)) {
$messages = $event->getMessages();
foreach ($messages as $message) {
// Do something
}
}
}
}
Email transports
----------------
Mautic supports quite some Email providers out of the box (Amazon Simple Email Service, SendGrid, etc.).
If you want to add your own Email transport, that's certainly possible.
The most important thing here is to create a service that's tagged as ``mautic.email.transport_type``, so that Mautic recognizes it as a transport type.
.. code-block:: PHP
<?php
// plugins/HelloWorldBundle/Config/config.php
declare(strict_types=1);
return [
...
'services' => [
...
'other' => [
'mautic.transport.helloworld_api' => [
'class' => \MauticPlugin\HelloWorldBundle\Swiftmailer\Transport\HelloWorldApiTransport::class,
'serviceAlias' => 'swiftmailer.mailer.transport.%s',
'arguments' => [
'mautic.helper.core_parameters',
],
'tag' => 'mautic.email_transport',
'tagArguments' => [
# Translatable alias that is used as an internal key for the transport type, but also as the translation key.
\Mautic\EmailBundle\Model\TransportType::TRANSPORT_ALIAS => 'mautic.email.config.mailer_transport.helloworld_api',
# Determines which fields to show in Mautic's configuration screen (under Email Settings)
\Mautic\EmailBundle\Model\TransportType::FIELD_HOST => true,
\Mautic\EmailBundle\Model\TransportType::FIELD_API_KEY => true,
\Mautic\EmailBundle\Model\TransportType::FIELD_PASSWORD => true,
\Mautic\EmailBundle\Model\TransportType::FIELD_PORT => true,
\Mautic\EmailBundle\Model\TransportType::FIELD_USER => true
],
],
],
],
];
The actual implementation of the service would then look something like this:
.. code-block:: PHP
<?php
// plugin/HelloWorldBundle/Swiftmailer/Transport/HelloeWorldApiTransport.php
declare(strict_types=1);
namespace MauticPlugin\HelloWorldBundle\Swiftmailer\Transport;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\EmailBundle\Swiftmailer\Transport\AbstractTokenArrayTransport;
use Mautic\EmailBundle\Swiftmailer\Transport\CallbackTransportInterface;
use Symfony\Component\HttpFoundation\Request;
class HelloWorldApiTransport extends AbstractTokenArrayTransport implements \Swift_Transport, CallbackTransportInterface
{
private CoreParametersHelper $coreParametersHelper;
public function __construct(CoreParametersHelper $coreParametersHelper)
{
$this->coreParametersHelper = $coreParametersHelper;
}
/**
* @return int
*
* @throws \Exception
*/
public function send(\Swift_Mime_SimpleMessage $message, &$failedRecipients = null)
{
$count = 0;
$failedRecipients = (array) $failedRecipients;
if ($event = $this->getDispatcher()->createSendEvent($this, $message)) {
$this->getDispatcher()->dispatchEvent($event, 'beforeSendPerformed');
if ($event->bubbleCancelled()) {
return 0;
}
}
try {
// The message object contains all the email details (from/to/body/etc.)
$from = $message->getFrom();
$to = $message->getTo();
$body = $message->getBody();
// Configuration values that were set by the user through Mautic's Configuration screen
$host = $this->coreParametersHelper->get('mautic.mailer_host');
$apiKey = $this->coreParametersHelper->get('mautic.mailer_api_key');
// Do your magic for sending the email here
// $myService->send(...)
// Return the number of recipients who were accepted for delivery
return 1;
} catch (\Exception $e) {
$this->triggerSendError($event, $failedRecipients);
$message->generateId();
$this->throwException($e->getMessage());
}
// Return the number of recipients who were accepted for delivery
return 0;
}
/**
* @inheritdoc
*/
public function getMaxBatchLimit(): int
{
return 50;
}
/**
* @inheritdoc
*/
public function getBatchRecipientCount(\Swift_Message $message, $toBeAdded = 1, $type = 'to'): int
{
$toCount = is_array($message->getTo()) ? count($message->getTo()) : 0;
$ccCount = is_array($message->getCc()) ? count($message->getCc()) : 0;
$bccCount = is_array($message->getBcc()) ? count($message->getBcc()) : 0;
return null === $this->batchRecipientCount ? $this->batchRecipientCount : $toCount + $ccCount + $bccCount + $toBeAdded;
}
/**
* @inheritdoc
*/
public function getCallbackPath(): string
{
return 'helloworld_api';
}
/**
* @inheritdoc
*/
public function processCallbackRequest(Request $request)
{
$postData = json_decode($request->getContent(), true);
// Handle the callback here
}
private function triggerSendError(\Swift_Events_SendEvent $evt, array &$failedRecipients): void
{
$failedRecipients = array_merge(
$failedRecipients,
array_keys((array) $this->message->getTo()),
array_keys((array) $this->message->getCc()),
array_keys((array) $this->message->getBcc())
);
if ($evt) {
$evt->setResult(\Swift_Events_SendEvent::RESULT_FAILED);
$evt->setFailedRecipients($failedRecipients);
$this->getDispatcher()->dispatchEvent($evt, 'sendPerformed');
}
}
}
Email stat helpers
------------------
This section is in progress. See ``\Mautic\EmailBundle\Stats\Helper\StatHelperInterface``
.. vale off
Testing Email transports
************************
.. vale on
This document targets software developers who write Email transports based on ``Symfony Mailer`` which is available to Mautic from Mautic 5.0.
This document describes Manual steps for testing and the items that you need to verify before submitting your PR for approval in case you want to add a new transport.
Email components
****************
Each Email sent out by Mautic includes the following components:
#. **Email Address:** (``FROM``, ``TO``, ``CC``, ``BCC``, ``REPLY-TO``): ``Unicode`` Email address in this format ``[email protected]`` or ``[email protected]``. Make sure that you always use the Unicode email address to accommodate special characters in languages like Arabic, Hebrew, or Chinese.
#. **Email Name:** (``FROM``, ``TO``, ``CC``, ``BCC``, ``REPLY-TO``): ``Unicode`` Human-readable name, make sure that you always use Unicode Email address to accommodate special characters in languages like Arabic, Hebrew, or Chinese.
#. **Subject:** ``Unicode`` string that might include emojis.
#. **Text:** ``Unicode`` string that might include emojis.
#. **HTML:** ``Unicode`` string that might include emojis, in HTML format.
#. **Headers:** ``ANSI`` string pairs, ``Symfony/Mailer`` adds most of the headers, but for some transports, you need to add your own headers, so you can use the methods mentioned here: https://symfony.com/doc/current/mailer.html#message-headers, referenced in this file https://github.com/symfony/symfony/blob/HEAD/src/Symfony/Component/Mime/Header/Headers.php.
#. **Priority:** sets the Email priority based on ``enum``
#. **Attachments:** a file with a variety of mime types, the file size shouldn't exceed a specific size provided by the transport provider, usually nothing more than 10 MB and go up to 40 MB (for the whole message, including the text, HTML, and anything embedded within the HTML)
Preparing Mautic for testing
****************************
#. Create 10 Contacts with any Email address you need
#. Create a Segment that includes the 10 Contacts
.. vale off
Testing Email transport
***********************
.. vale on
In order to test the Email transport you need to go through the following steps:
Testing the connection
======================
Go to Mautic Configuration -> Email Settings -> Click on Test Connection. If the connection works you should see **success** otherwise you should see an **error**
.. image:: images/test-connection.png
:width: 600
:alt: Screenshot showing testing the connection
.. vale off
Sending a sample Email
======================
.. vale on
From the same screen where you test the connection, you can send a sample Email. Mautic sends the sample Email to the address of the currently logged in Mautic User. Check that the Email arrives.
.. vale off
Upload an Asset
===============
.. vale on
Go to Components -> Assets and then upload a sample file and make sure the filename uses one of the Unicode languages - such as Arabic, Russian, German, etc.
.. vale off
Create a template Email
=======================
.. vale on
Go to Channels -> Emails -> New -> Template Email -> Select Blank Theme
Use the builder to do the following:
- Embed an image
- Add Unicode text, you can use this "نحن نحب ان نقوم ببناء Mautic"
- Close the builder
- Go to the Advanced tab
- Complete the ``From Name`` & ``From Address``, ``BCC``, ``Reply-To``, ``Add Attachment``, ``custom headers``, and Click on ``Auto Generate`` to create a text version of the Email
- Save the Email and send a sample test, you should get everything you filled
Create a Segment Email
======================
Go to Channels -> Emails -> New -> Segment Email -> Select Blank Theme
Use the builder to do the following:
- Embed an image
- Add Unicode text, you can use this "نحن نحب ان نقوم ببناء Mautic"
- Close the builder,
- Go to the Advanced tab
- Complete the ``From Name`` & ``From Address``, ``BCC``, ``Reply-To``, ``Add Attachment``, ``custom headers``, and Click on ``Auto Generate`` to create a text version of the Email
- Save the Email and send a sample test, you should get everything you filled
.. vale off
Send an individual Email
========================
.. vale on
Go to the Contacts section and select a Contact, then click Send an Email. You should be able to send an Email directly to that specific Contact's Email address.
Send a Report Email
===================
Create a Report with any data and set it on a schedule, it should send an Email with the Report as an attachment
.. vale off
Other Email features
====================
.. vale on
There are other places like Forget Password: they need to work as well. Please make sure you verify them.
Testing transport callback
**************************
Each transport should include a callback URL which Webhooks should be ``POSTed`` to, which marks Contacts who bounce as ``Do Not Contact``.
To test these callbacks you need to do the following:
#. Configure an Email transport and make it the default transport
#. Go to the URL on the following format ``/mailer/{transport}/callback``
#. You should get a message that says ``success`` and there should be a callback logic to handle the Webhook