Edit: This question arose in the attempt to have both synchronous and synchronous emails in the same application. That was not made clear. As of this writing it is not possible, at least not as simply as tried here. See comments by @msg below.
An email service configured to send email asynchronously instead sends emails immediately. This happens with either doctrine
or amqp
selected as MESSENGER_TRANSPORT_DSN
. doctrine
transport successfully creates messenger_messages
table, but with no contents. This tells me the MESSENGER_TRANSPORT_DSN
is observed. Simple test of amqp
using RabbitMQ 'Hello World' tutorial shows it is properly configured.
What have I missed in the code below?
Summary of sequence shown below: Opportunity added -> OppEmailService
creates email contents -> gets TemplatedEmail()
object from EmailerService
(not shown) -> submits TemplatedEmail()
object to LaterEmailService
, which is configured to be async.
messenger.yaml:
framework:
messenger:
transports:
async: '%env(MESSENGER_TRANSPORT_DSN)%'
sync: 'sync://'
routing:
'App\Services\NowEmailService': sync
'App\Services\LaterEmailService': async
OpportunityController
:
class OpportunityController extends AbstractController
{
private $newOpp;
private $templateSvc;
public function __construct(OppEmailService $newOpp, TemplateService $templateSvc)
{
$this->newOpp = $newOpp;
$this->templateSvc = $templateSvc;
}
...
public function addOpp(Request $request): Response
{
...
if ($form->isSubmitted() && $form->isValid()) {
...
$volunteers = $em->getRepository(Person::class)->opportunityEmails($opportunity);
$this->newOpp->oppEmail($volunteers, $opportunity);
...
}
OppEmailService
:
class OppEmailService
{
private $em;
private $makeMail;
private $laterMail;
public function __construct(
EmailerService $makeMail,
EntityManagerInterface $em,
LaterEmailService $laterMail
)
{
$this->makeMail = $makeMail;
$this->em = $em;
$this->laterMail = $laterMail;
}
...
public function oppEmail($volunteers, $opp): array
{
...
$mailParams = [
'template' => 'Email/volunteer_opportunities.html.twig',
'context' => ['fname' => $person->getFname(), 'opportunity' => $opp,],
'recipient' => $person->getEmail(),
'subject' => 'New volunteer opportunity',
];
$toBeSent = $this->makeMail->assembleEmail($mailParams);
$this->laterMail->send($toBeSent);
...
}
}
LaterEmailService
:
namespace App\Services;
use Symfony\Component\Mailer\MailerInterface;
class LaterEmailService
{
private $mailer;
public function __construct(MailerInterface $mailer)
{
$this->mailer = $mailer;
}
public function send($email)
{
$this->mailer->send($email);
}
}
CodePudding user response:
I ended up creating console commands to be run as daily cron
jobs. Each command calls a services that create and send emails. The use case is a low volume daily email to registered users informing them of actions that affect them. An example follows:
Console command:
class NewOppsEmailCommand extends Command
{
private $mailer;
private $oppEmail;
private $twig;
public function __construct(OppEmailService $oppEmail, EmailerService $mailer, Environment $twig)
{
$this->mailer = $mailer;
$this->oppEmail = $oppEmail;
$this->twig = $twig;
parent::__construct();
}
protected static $defaultName = 'app:send:newoppsemaiils';
protected function configure()
{
$this->setDescription('Sends email re: new opps to registered');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$emails = $this->oppEmail->oppEmail();
$output->writeln($emails . ' email(s) were sent');
return COMMAND::SUCCESS;
}
}
OppEmailService:
class OppEmailService
{
private $em;
private $mailer;
public function __construct(EmailerService $mailer, EntityManagerInterface $em)
{
$this->mailer = $mailer;
$this->em = $em;
}
/**
* Send new opportunity email to registered volunteers
*/
public function oppEmail()
{
$unsentEmail = $this->em->getRepository(OppEmail::class)->findAll(['sent' => false], ['volunteer' => 'ASC']);
if (empty($unsentEmail)) {
return 0;
}
$email = 0;
foreach ($unsentEmail as $recipient) {
$mailParams = [
'template' => 'Email/volunteer_opportunities.html.twig',
'context' => [
'fname' => $recipient->getVolunteer()->getFname(),
'opps' => $recipient->getOpportunities(),
],
'recipient' => $recipient->getVolunteer()->getEmail(),
'subject' => 'New opportunities',
];
$this->mailer->assembleEmail($mailParams);
$recipient->setSent(true);
$this->em->persist($recipient);
$email ;
}
$this->em->flush();
return $email;
}
}
EmailerService:
class EmailerService
{
private $em;
private $mailer;
public function __construct(EntityManagerInterface $em, MailerInterface $mailer)
{
$this->em = $em;
$this->mailer = $mailer;
}
public function assembleEmail($mailParams)
{
$sender = $this->em->getRepository(Person::class)->findOneBy(['mailer' => true]);
$email = (new TemplatedEmail())
->to($mailParams['recipient'])
->from($sender->getEmail())
->subject($mailParams['subject'])
->htmlTemplate($mailParams['template'])
->context($mailParams['context'])
;
$this->mailer->send($email);
return $email;
}
}