Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PPF-53 Add basis for sending transactional emails #1329

Merged
merged 20 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions app/Domain/Mail/Addresses.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace App\Domain\Mail;

use Illuminate\Support\Collection;
use Symfony\Component\Mime\Address;

/**
* @extends Collection<int, Address>
*/
final class Addresses extends Collection
{
}
80 changes: 80 additions & 0 deletions app/Domain/Mail/MailManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

declare(strict_types=1);

namespace App\Domain\Mail;

use App\Domain\Contacts\Contact;
use App\Domain\Integrations\Events\IntegrationActivated;
use App\Domain\Integrations\Events\IntegrationBlocked;
use App\Domain\Integrations\Repositories\IntegrationRepository;
use Symfony\Component\Mime\Address;

final class MailManager
{
private const SUBJECT_INTEGRATION_ACTIVATED = 'Publiq platform - Integration activated';
private const SUBJECT_INTEGRATION_BLOCKED = 'Publiq platform - Integration blocked';

public function __construct(
private readonly Mailer $mailer,
private readonly IntegrationRepository $integrationRepository,
private readonly int $templateIntegrationActivated,
private readonly int $templateIntegrationBlocked,
private readonly string $baseUrl
) {
}

public function sendIntegrationActivatedMail(IntegrationActivated $integrationActivated): void
{
$integration = $this->integrationRepository->getById($integrationActivated->id);

foreach ($integration->contacts() as $contact) {
$this->mailer->send(
$this->getFrom(),
$this->getAddresses($contact),
$this->templateIntegrationActivated,
self::SUBJECT_INTEGRATION_ACTIVATED,
[
'firstName' => $contact->firstName,
'lastName' => $contact->lastName,
'contactType' => $contact->type->value,
'integrationName' => $integration->name,
'url' => $this->baseUrl . '/nl/integraties/' . $integration->id,
'type' => $integration->type->value,
]
);
}
}

public function sendIntegrationBlockedMail(IntegrationBlocked $integrationBlocked): void
{
$integration = $this->integrationRepository->getById($integrationBlocked->id);

foreach ($integration->contacts() as $contact) {

$this->mailer->send(
$this->getFrom(),
$this->getAddresses($contact),
$this->templateIntegrationBlocked,
self::SUBJECT_INTEGRATION_BLOCKED,
[
'firstName' => $contact->firstName,
'lastName' => $contact->lastName,
'contactType' => $contact->type->value,
'integrationName' => $integration->name,
]
);
}

}

private function getFrom(): Address
{
return new Address(config('mail.from.address'), config('mail.from.name'));
}

private function getAddresses(Contact $contact): Addresses
{
return new Addresses([new Address($contact->email, trim($contact->firstName . ' ' . $contact->lastName))]);
}
}
18 changes: 18 additions & 0 deletions app/Domain/Mail/MailNotSend.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace App\Domain\Mail;

final class MailNotSend extends \DomainException
LucWollants marked this conversation as resolved.
Show resolved Hide resolved
{
public function __construct(?string $reason = null, ?array $variables = null)
{
$reason = $reason ?? 'Unknown reason';
$message = $variables === null
? sprintf('Failed to send mail: %s', $reason)
: sprintf('Failed to send mail: %s with params: %s', $reason, json_encode($variables, JSON_THROW_ON_ERROR));

parent::__construct($message);
}
}
12 changes: 12 additions & 0 deletions app/Domain/Mail/Mailer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace App\Domain\Mail;

use Symfony\Component\Mime\Address;

interface Mailer
{
public function send(Address $from, Addresses $to, int $templateId, string $subject, array $variables = []): void;
}
15 changes: 15 additions & 0 deletions app/Mails/MailJet/MailjetConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace App\Mails\MailJet;

final class MailjetConfig
{
// This has a longer convoluted name because there is also a mailinglist integration with mailjet that is unrelated.
public const TRANSACTIONAL_EMAILS_ENABLED = 'mailjet.enabled';
public const API_KEY = 'mailjet.api.key';
public const API_SECRET = 'mailjet.api.secret';
public const TEMPLATE_INTEGRATION_BLOCKED = 'mailjet.templates.integration_blocked';
public const TEMPLATE_INTEGRATION_ACTIVATED = 'mailjet.templates.integration_activated';
}
78 changes: 78 additions & 0 deletions app/Mails/MailJet/MailjetMailer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace App\Mails\MailJet;

use App\Domain\Mail\Addresses;
use App\Domain\Mail\Mailer;
use App\Domain\Mail\MailNotSend;
use Mailjet\Client;
use Mailjet\Resources;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mime\Address;

final readonly class MailjetMailer implements Mailer
{
public function __construct(
private Client $client,
private LoggerInterface $logger
) {

}

public function send(Address $from, Addresses $to, int $templateId, string $subject, array $variables = []): void
{
$body = [
'Messages' => [
[
'From' => [
'Email' => $from->getAddress(),
'Name' => $from->getName(),
],
'To' => $this->buildReceiverList($to),
'TemplateID' => $templateId,
'TemplateLanguage' => true,
'Subject' => $subject,
'Variables' => $variables,
],
],
];

$response = $this->client->post(
Resources::$Email,
['body' => $body]
);

if (!$response->success()) {
$this->logger->debug((string)json_encode($response->getData(), JSON_THROW_ON_ERROR));
throw new MailNotSend($response->getReasonPhrase(), $variables);
}

$this->logger->info(
sprintf(
'Mail "%s" sent successfully to %s',
$subject,
implode(', ', $to->map(
function (Address $address) {
return $address->getAddress();
}
)->toArray())
)
);
}

private function buildReceiverList(Addresses $addresses): array
{
$output = [];

foreach ($addresses as $address) {
$output[] = [
'Email' => $address->getAddress(),
'Name' => $address->getName(),
];
}

return $output;
}
}
53 changes: 53 additions & 0 deletions app/Mails/MailServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace App\Mails;

use App\Domain\Integrations\Events\IntegrationActivated;
use App\Domain\Integrations\Events\IntegrationBlocked;
use App\Domain\Integrations\Repositories\IntegrationRepository;
use App\Domain\Mail\Mailer;
use App\Domain\Mail\MailManager;
use App\Mails\MailJet\MailjetConfig;
use App\Mails\MailJet\MailjetMailer;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider;
use Mailjet\Client;
use Psr\Log\LoggerInterface;

final class MailServiceProvider extends ServiceProvider
{
public function register(): void
{
if (!config(MailjetConfig::TRANSACTIONAL_EMAILS_ENABLED)) {
return;
}

$this->app->singleton(Mailer::class, function () {
return new MailjetMailer(
new Client(
config(MailjetConfig::API_KEY),
config(MailjetConfig::API_SECRET),
true,
['version' => 'v3.1']
),
$this->app->get(LoggerInterface::class)
);
});


$this->app->singleton(MailManager::class, function () {
return new MailManager(
$this->app->get(Mailer::class),
$this->app->get(IntegrationRepository::class),
(int)config(MailjetConfig::TEMPLATE_INTEGRATION_ACTIVATED),
(int)config(MailjetConfig::TEMPLATE_INTEGRATION_BLOCKED),
config('url')
);
});

Event::listen(IntegrationActivated::class, [MailManager::class, 'sendIntegrationActivatedMail']);
Event::listen(IntegrationBlocked::class, [MailManager::class, 'sendIntegrationBlockedMail']);
}
}
1 change: 1 addition & 0 deletions config/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@
App\ProjectAanvraag\ProjectAanvraagServiceProvider::class,
App\Keycloak\KeycloakServiceProvider::class,
App\Search\SearchServiceProvider::class,
\App\Mails\MailServiceProvider::class,
\App\Notifications\NotificationsProvider::class,
],

Expand Down
16 changes: 16 additions & 0 deletions config/mailjet.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);


return [
'enabled' => env('MAILJET_TRANSACTIONAL_EMAILS_ENABLED', false),
'api' => [
'key' => env('MAILJET_API_KEY'),
'secret' => env('MAILJET_API_SECRET'),
],
'templates' => [
'integration_blocked' => env('MAILJET_TEMPLATE_INTEGRATION_BLOCKED'),
'integration_activated' => env('MAILJET_TEMPLATE_INTEGRATION_ACTIVATED'),
],
];
Loading
Loading