Skip to content

Commit 6bdd8b5

Browse files
committed
Send Emails in Background
1 parent 182bf17 commit 6bdd8b5

File tree

8 files changed

+244
-21
lines changed

8 files changed

+244
-21
lines changed

.env.example

+3-1
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ DB_USER=db
3232
DB_PASSWORD=db
3333

3434
#
35-
# SMTP Configuraion
35+
# Mailer Configuration
36+
# MAILER_MAX_BATCH_SIZE configures, how many mails the background mailer is allowed to send at once
3637
#
3738
MAILER_HOST=mail.example.com
3839
MAILER_PORT=25
3940
MAILER_USERNAME=info@example.com
4041
MAILER_PASSWORD=password
4142
MAILER_DISPLAY_EMAIL=info@example.com
4243
MAILER_DISPLAY_FROM="Web Toolkit"
44+
MAILER_MAX_BATCH_SIZE=25

config/container.php

+4
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153

154154
$container->add(\App\Service\MailerService::class)
155155
->addArgument(\App\Factory\MailerFactory::class)
156+
->addArgument(\App\Table\Mail\MailTable::class)
156157
->addArgument(\League\Plates\Engine::class)
157158
->addArgument(\Monolog\Logger::class);
158159

@@ -183,6 +184,9 @@
183184
$container->add(\App\Table\Authentication\TokenTable::class)
184185
->addArgument(\Envms\FluentPDO\Query::class);
185186

187+
$container->add(\App\Table\Mail\MailTable::class)
188+
->addArgument(\Envms\FluentPDO\Query::class);
189+
186190
#
187191
# Validations
188192
#

src/App/Controller/Authentication/LostPasswordController.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use App\Http\HtmlResponse;
66
use App\Model\Authentication\Account;
77
use App\Model\Authentication\Token;
8+
use App\Model\Mail\MailType;
89
use App\Service\Authentication\AccountService;
910
use App\Service\MailerService;
1011
use League\Plates\Engine;
@@ -49,7 +50,7 @@ public function resetPassword(ServerRequestInterface $request): void
4950
$this->mailerService->configureMail(
5051
$account->getEmail(),
5152
'Reset Password',
52-
'resetPassword',
53+
MailType::RESET_PASSWORD_MAIL_ID,
5354
['token' => $token->getToken()]
5455
)->send();
5556
}

src/App/Controller/Authentication/RegistrationController.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use App\Model\Authentication\AccountLevel;
1010
use App\Model\Authentication\Token;
1111
use App\Model\Authentication\TokenType;
12+
use App\Model\Mail\MailType;
1213
use App\Service\Authentication\AccountService;
1314
use App\Service\Authentication\TokenService;
1415
use App\Service\MailerService;
@@ -64,7 +65,7 @@ private function register(ServerRequestInterface $request): void
6465
$this->mailerService->configureMail(
6566
$account->getEmail(),
6667
'Account aktivieren',
67-
'activateAccount',
68+
MailType::ACTIVATION_MAIL_ID,
6869
['token' => $token->getToken(), 'name' => $account->getName()]
6970
)->send();
7071
}

src/App/Model/Mail/Mail.php

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace App\Model\Mail;
4+
5+
use DateTime;
6+
7+
class Mail
8+
{
9+
10+
private int $id;
11+
private int $type;
12+
private ?int $account;
13+
private string $email;
14+
private string $subject;
15+
private array $content;
16+
private DateTime $created;
17+
private ?DateTime $sent;
18+
19+
public function getId(): int
20+
{
21+
return $this->id;
22+
}
23+
24+
public function setId(int $id): void
25+
{
26+
$this->id = $id;
27+
}
28+
29+
public function getType(): int
30+
{
31+
return $this->type;
32+
}
33+
34+
public function setType(int $type): void
35+
{
36+
$this->type = $type;
37+
}
38+
39+
public function getAccount(): ?int
40+
{
41+
return $this->account;
42+
}
43+
44+
public function setAccount(?int $account): void
45+
{
46+
$this->account = $account;
47+
}
48+
49+
public function getContent(): array
50+
{
51+
return $this->content;
52+
}
53+
54+
public function setContent(array $content): void
55+
{
56+
$this->content = $content;
57+
}
58+
59+
public function getEmail(): string
60+
{
61+
return $this->email;
62+
}
63+
64+
public function setEmail(string $email): void
65+
{
66+
$this->email = $email;
67+
}
68+
69+
public function getSubject(): string
70+
{
71+
return $this->subject;
72+
}
73+
74+
public function setSubject(string $subject): void
75+
{
76+
$this->subject = $subject;
77+
}
78+
79+
public function getCreated(): DateTime
80+
{
81+
return $this->created;
82+
}
83+
84+
public function setCreated(DateTime $created): void
85+
{
86+
$this->created = $created;
87+
}
88+
89+
public function getSent(): ?DateTime
90+
{
91+
return $this->sent;
92+
}
93+
94+
public function setSent(?DateTime $sent): void
95+
{
96+
$this->sent = $sent;
97+
}
98+
99+
}

src/App/Model/Mail/MailType.php

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace App\Model\Mail;
4+
5+
class MailType
6+
{
7+
8+
public const ACTIVATION_MAIL_ID = 1;
9+
public const RESET_PASSWORD_MAIL_ID = 2;
10+
11+
private int $id;
12+
private string $title;
13+
private string $template;
14+
15+
public function getId(): int
16+
{
17+
return $this->id;
18+
}
19+
20+
public function setId(int $id): void
21+
{
22+
$this->id = $id;
23+
}
24+
25+
public function getTitle(): string
26+
{
27+
return $this->title;
28+
}
29+
30+
public function setTitle(string $title): void
31+
{
32+
$this->title = $title;
33+
}
34+
35+
public function getTemplate(): string
36+
{
37+
return $this->template;
38+
}
39+
40+
public function setTemplate(string $template): void
41+
{
42+
$this->template = $template;
43+
}
44+
45+
}

src/App/Service/MailerService.php

+51-18
Original file line numberDiff line numberDiff line change
@@ -4,41 +4,74 @@
44
namespace App\Service;
55

66
use App\Factory\MailerFactory;
7+
use App\Model\Mail\Mail;
8+
use App\Table\Mail\MailTable;
79
use League\Plates\Engine;
810
use Monolog\Logger;
911
use PHPMailer\PHPMailer\Exception;
1012

11-
readonly class MailerService
13+
class MailerService
1214
{
1315

16+
private Mail $mail;
17+
1418
public function __construct(
15-
private MailerFactory $mailer,
16-
private Engine $template,
17-
private Logger $logger
19+
private readonly MailerFactory $mailer,
20+
private readonly MailTable $mailTable,
21+
private readonly Engine $template,
22+
private readonly Logger $logger
1823
) {
1924
}
2025

21-
public function configureMail(string $to, string $subject, string $template, array $content = []): self
26+
public function configureMail(string $to, string $subject, int $template, array $content = [], int $account = null): self
2227
{
23-
$this->mailer->getMailer()->addAddress($to);
24-
25-
$this->mailer->getMailer()->isHTML();
26-
27-
$this->mailer->getMailer()->Subject = $subject;
28-
$this->mailer->getMailer()->Body = $this->template->render('mail/' . $template, $content);
28+
$this->mail = new Mail();
29+
$this->mail->setEmail($to);
30+
$this->mail->setAccount($account);
31+
$this->mail->setType($template);
32+
$this->mail->setSubject($subject);
33+
$this->mail->setContent($content);
2934

3035
return $this;
3136
}
3237

33-
public function send(): bool
38+
public function send(): void
3439
{
35-
try {
36-
$this->mailer->getMailer()->send();
37-
return true;
38-
} catch (Exception) {
39-
$this->logger->error($this->mailer->getMailer()->ErrorInfo);
40-
return false;
40+
$this->mailTable->insert($this->mail);
41+
}
42+
43+
public function fetchMailsAndSend(): void
44+
{
45+
$this->logger->info('Starting Mail Batch');
46+
$mails = $this->mailTable->findAllNotSentLimit();
47+
48+
$amount = count($mails);
49+
$this->logger->info('Collected a batch of ' . $amount . ' Mails.');
50+
$success = 0;
51+
52+
foreach ($mails as $mail) {
53+
54+
$time = microtime(true);
55+
$this->mailer->getMailer()->isHTML();
56+
$this->mailer->getMailer()->Subject = $mail['subject'];
57+
$this->mailer->getMailer()->addAddress($mail['email']);
58+
$this->mailer->getMailer()->Body =
59+
$this->template->render('mail/'.$mail['template'], json_decode($mail['content'], true));
60+
61+
try {
62+
$this->mailer->getMailer()->send();
63+
$this->mailTable->updateMailSentById($mail['id']);
64+
$success++;
65+
$this->logger->info('Mail Sending took ' . microtime(true) - $time . 'ms ('.$success.'/'.$amount.')');
66+
} catch (Exception)
67+
{
68+
$this->logger->error('Mail Sending failed after ' . microtime() - $time . 'ms', [$this->mailer->getMailer()->ErrorInfo]);
69+
}
70+
4171
}
72+
73+
$this->logger->info('Mail Batch complete. Sent a total of ' . $success . ' E-Mails');
74+
4275
}
4376

4477
}

src/App/Table/Mail/MailTable.php

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace App\Table\Mail;
4+
5+
use App\Model\Mail\Mail;
6+
use App\Software;
7+
use App\Table\AbstractTable;
8+
9+
class MailTable extends AbstractTable
10+
{
11+
12+
public function insert(Mail $mail): bool
13+
{
14+
$values = [
15+
'type' => $mail->getType(),
16+
'account' => $mail->getAccount(),
17+
'email' => $mail->getEmail(),
18+
'subject' => $mail->getSubject(),
19+
'content' => json_encode($mail->getContent())
20+
];
21+
return $this->query->insertInto($this->getTableName())->values($values)->executeWithoutId();
22+
}
23+
24+
public function findAllNotSentLimit(): array|bool
25+
{
26+
return $this->query->from($this->getTableName())->where('sent', NULL)
27+
->select('MailType.template')
28+
->leftJoin('MailType on MailType.id = Mail.type')
29+
->limit($_ENV['MAILER_MAX_BATCH_SIZE'])
30+
->orderBy('created ASC')->fetchAll();
31+
}
32+
33+
public function updateMailSentById(int $id): void
34+
{
35+
$this->query->update($this->getTableName())->set('sent', (new \DateTime())->format(Software::DATABASE_TIME_FORMAT))->where('id',$id)->execute();
36+
}
37+
38+
}

0 commit comments

Comments
 (0)