Skip to content

ISSUE-12: subscription confirmation email #342

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

Merged
merged 4 commits into from
Jun 20, 2025
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
1 change: 1 addition & 0 deletions config/packages/messenger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ framework:
routing:
# Route your messages to the transports
'PhpList\Core\Domain\Messaging\Message\AsyncEmailMessage': async_email
'PhpList\Core\Domain\Messaging\Message\SubscriberConfirmationMessage': async_email
8 changes: 7 additions & 1 deletion config/parameters.yml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,17 @@ parameters:
database_password: '%%env(PHPLIST_DATABASE_PASSWORD)%%'
env(PHPLIST_DATABASE_PASSWORD): 'phplist'

# mailer configs
# Email configuration
app.mailer_from: '%%env(MAILER_FROM)%%'
env(MAILER_FROM): '[email protected]'
app.mailer_dsn: '%%env(MAILER_DSN)%%'
env(MAILER_DSN): 'smtp://username:[email protected]:2525'
app.confirmation_url: '%%env(CONFIRMATION_URL)%%'
env(CONFIRMATION_URL): 'https://example.com/confirm/'

# Messenger configuration for asynchronous processing
app.messenger_transport_dsn: '%%env(MESSENGER_TRANSPORT_DSN)%%'
env(MESSENGER_TRANSPORT_DSN): 'doctrine://default?auto_setup=true'

# A secret key that's used to generate certain security-related tokens
secret: '%%env(PHPLIST_SECRET)%%'
Expand Down
5 changes: 0 additions & 5 deletions config/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ services:
public: true
tags: [controller.service_arguments]

# Register message handlers for Symfony Messenger
PhpList\Core\Domain\Messaging\MessageHandler\:
resource: '../src/Domain/Messaging/MessageHandler'
tags: ['messenger.message_handler']

doctrine.orm.metadata.annotation_reader:
alias: doctrine.annotation_reader

Expand Down
16 changes: 0 additions & 16 deletions config/services/managers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,6 @@ services:
autowire: true
autoconfigure: true

PhpList\Core\Domain\Subscription\Service\SubscriberCsvExporter:
autowire: true
autoconfigure: true
public: true

PhpList\Core\Domain\Subscription\Service\SubscriberCsvImporter:
autowire: true
autoconfigure: true
public: true

PhpList\Core\Domain\Messaging\Service\EmailService:
autowire: true
autoconfigure: true
arguments:
$defaultFromEmail: '%app.mailer_from%'

PhpList\Core\Domain\Configuration\Service\Manager\ConfigManager:
autowire: true
autoconfigure: true
12 changes: 12 additions & 0 deletions config/services/messenger.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
services:
# Register message handlers for Symfony Messenger
PhpList\Core\Domain\Messaging\MessageHandler\:
resource: '../../src/Domain/Messaging/MessageHandler'
tags: [ 'messenger.message_handler' ]

PhpList\Core\Domain\Messaging\MessageHandler\SubscriberConfirmationMessageHandler:
autowire: true
autoconfigure: true
tags: [ 'messenger.message_handler' ]
arguments:
$confirmationUrl: '%app.confirmation_url%'
16 changes: 16 additions & 0 deletions config/services/services.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
services:
PhpList\Core\Domain\Subscription\Service\SubscriberCsvExporter:
autowire: true
autoconfigure: true
public: true

PhpList\Core\Domain\Subscription\Service\SubscriberCsvImporter:
autowire: true
autoconfigure: true
public: true

PhpList\Core\Domain\Messaging\Service\EmailService:
autowire: true
autoconfigure: true
arguments:
$defaultFromEmail: '%app.mailer_from%'
43 changes: 43 additions & 0 deletions src/Domain/Messaging/Message/SubscriberConfirmationMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace PhpList\Core\Domain\Messaging\Message;

/**
* Message class for asynchronous subscriber confirmation email processing
*/
class SubscriberConfirmationMessage
{
private string $email;
private string $uniqueId;
private bool $htmlEmail;

/**
* @SuppressWarnings("BooleanArgumentFlag")
*/
public function __construct(
string $email,
string $uniqueId,
bool $htmlEmail = false
) {
$this->email = $email;
$this->uniqueId = $uniqueId;
$this->htmlEmail = $htmlEmail;
}

public function getEmail(): string
{
return $this->email;
}

public function getUniqueId(): string
{
return $this->uniqueId;
}

public function hasHtmlEmail(): bool
{
return $this->htmlEmail;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

namespace PhpList\Core\Domain\Messaging\MessageHandler;

use PhpList\Core\Domain\Messaging\Message\SubscriberConfirmationMessage;
use PhpList\Core\Domain\Messaging\Service\EmailService;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Mime\Email;

/**
* Handler for processing asynchronous subscriber confirmation email messages
*/
#[AsMessageHandler]
class SubscriberConfirmationMessageHandler
{
private EmailService $emailService;
private string $confirmationUrl;

public function __construct(EmailService $emailService, string $confirmationUrl)
{
$this->emailService = $emailService;
$this->confirmationUrl = $confirmationUrl;
}

/**
* Process a subscriber confirmation message by sending the confirmation email
*/
public function __invoke(SubscriberConfirmationMessage $message): void
{
$confirmationLink = $this->generateConfirmationLink($message->getUniqueId());

$subject = 'Please confirm your subscription';
$textContent = "Thank you for subscribing!\n\n"
. "Please confirm your subscription by clicking the link below:\n"
. $confirmationLink . "\n\n"
. 'If you did not request this subscription, please ignore this email.';

$htmlContent = '';
if ($message->hasHtmlEmail()) {
$htmlContent = '<p>Thank you for subscribing!</p>'
. '<p>Please confirm your subscription by clicking the link below:</p>'
. '<p><a href="' . $confirmationLink . '">Confirm Subscription</a></p>'
. '<p>If you did not request this subscription, please ignore this email.</p>';
}

$email = (new Email())
->to($message->getEmail())
->subject($subject)
->text($textContent);

if (!empty($htmlContent)) {
$email->html($htmlContent);
}

$this->emailService->sendEmail($email);
}

/**
* Generate a confirmation link for the subscriber
*/
private function generateConfirmationLink(string $uniqueId): string
{
return $this->confirmationUrl . $uniqueId;
}
}
49 changes: 0 additions & 49 deletions src/Domain/Messaging/Service/EmailService.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace PhpList\Core\Domain\Messaging\Service;

use PhpList\Core\Domain\Messaging\Message\AsyncEmailMessage;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Mime\Email;
Expand All @@ -27,16 +26,6 @@ public function __construct(
$this->messageBus = $messageBus;
}

/**
* Send a simple email asynchronously
*
* @param Email $email
* @param array $cc
* @param array $bcc
* @param array $replyTo
* @param array $attachments
* @return void
*/
public function sendEmail(
Email $email,
array $cc = [],
Expand All @@ -52,17 +41,6 @@ public function sendEmail(
$this->messageBus->dispatch($message);
}

/**
* Send a simple email synchronously
*
* @param Email $email
* @param array $cc
* @param array $bcc
* @param array $replyTo
* @param array $attachments
* @return void
* @throws TransportExceptionInterface
*/
public function sendEmailSync(
Email $email,
array $cc = [],
Expand Down Expand Up @@ -93,19 +71,6 @@ public function sendEmailSync(
$this->mailer->send($email);
}

/**
* Email multiple recipients asynchronously
*
* @param array $toAddresses Array of recipient email addresses
* @param string $subject Email subject
* @param string $text Plain text content
* @param string $html HTML content (optional)
* @param string|null $from Sender email address (optional, uses default if not provided)
* @param string|null $fromName Sender name (optional)
* @param array $attachments Array of file paths to attach (optional)
*
* @return void
*/
public function sendBulkEmail(
array $toAddresses,
string $subject,
Expand All @@ -132,20 +97,6 @@ public function sendBulkEmail(
}
}

/**
* Email multiple recipients synchronously
*
* @param array $toAddresses Array of recipient email addresses
* @param string $subject Email subject
* @param string $text Plain text content
* @param string $html HTML content (optional)
* @param string|null $from Sender email address (optional, uses default if not provided)
* @param string|null $fromName Sender name (optional)
* @param array $attachments Array of file paths to attach (optional)
*
* @return void
* @throws TransportExceptionInterface
*/
public function sendBulkEmailSync(
array $toAddresses,
string $subject,
Expand Down
26 changes: 24 additions & 2 deletions src/Domain/Subscription/Service/Manager/SubscriberManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,29 @@
namespace PhpList\Core\Domain\Subscription\Service\Manager;

use Doctrine\ORM\EntityManagerInterface;
use PhpList\Core\Domain\Messaging\Message\SubscriberConfirmationMessage;
use PhpList\Core\Domain\Subscription\Model\Dto\CreateSubscriberDto;
use PhpList\Core\Domain\Subscription\Model\Dto\ImportSubscriberDto;
use PhpList\Core\Domain\Subscription\Model\Dto\UpdateSubscriberDto;
use PhpList\Core\Domain\Subscription\Model\Subscriber;
use PhpList\Core\Domain\Subscription\Repository\SubscriberRepository;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Messenger\MessageBusInterface;

class SubscriberManager
{
private SubscriberRepository $subscriberRepository;
private EntityManagerInterface $entityManager;
private MessageBusInterface $messageBus;

public function __construct(SubscriberRepository $subscriberRepository, EntityManagerInterface $entityManager)
{
public function __construct(
SubscriberRepository $subscriberRepository,
EntityManagerInterface $entityManager,
MessageBusInterface $messageBus
) {
$this->subscriberRepository = $subscriberRepository;
$this->entityManager = $entityManager;
$this->messageBus = $messageBus;
}

public function createSubscriber(CreateSubscriberDto $subscriberDto): Subscriber
Expand All @@ -35,9 +42,24 @@ public function createSubscriber(CreateSubscriberDto $subscriberDto): Subscriber

$this->subscriberRepository->save($subscriber);

if ($subscriberDto->requestConfirmation) {
$this->sendConfirmationEmail($subscriber);
}

return $subscriber;
}

private function sendConfirmationEmail(Subscriber $subscriber): void
{
$message = new SubscriberConfirmationMessage(
email: $subscriber->getEmail(),
uniqueId:$subscriber->getUniqueId(),
htmlEmail: $subscriber->hasHtmlEmail()
);

$this->messageBus->dispatch($message);
}

public function getSubscriber(int $subscriberId): Subscriber
{
$subscriber = $this->subscriberRepository->findSubscriberWithSubscriptions($subscriberId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace PhpList\Core\Tests\Unit\Domain\Messaging\Message;

use PhpList\Core\Domain\Messaging\Message\SubscriberConfirmationMessage;
use PHPUnit\Framework\TestCase;

class SubscriberConfirmationMessageTest extends TestCase
{
public function testGettersReturnCorrectValues(): void
{
$email = '[email protected]';
$uniqueId = 'abc123';
$htmlEmail = true;

$message = new SubscriberConfirmationMessage($email, $uniqueId, $htmlEmail);

$this->assertSame($email, $message->getEmail());
$this->assertSame($uniqueId, $message->getUniqueId());
$this->assertTrue($message->hasHtmlEmail());
}

public function testDefaultHtmlEmailIsFalse(): void
{
$email = '[email protected]';
$uniqueId = 'abc123';

$message = new SubscriberConfirmationMessage($email, $uniqueId);

$this->assertFalse($message->hasHtmlEmail());
}
}
Loading
Loading