beecubu / php-foundation-emailer
PHP Object Foundation Framework Emailer library for SMTP, EWS and Microsoft Graph
Package info
bitbucket.org/beecubu/php-foundation-emailer
pkg:composer/beecubu/php-foundation-emailer
Requires
- php: >=7.1.0
- ext-curl: *
- ext-json: *
- beecubu/php-foundation-cli: v1.6
- beecubu/php-foundation-core: ^v3.7
- php-ews/php-ews: dev-master
- symfony/mailer: 5.4
Requires (Dev)
- phpunit/phpunit: ^8
README
PHP Object Foundation Emailer with SMTP, Microsoft Exchange Web Services (EWS) and Microsoft Graph API support. It includes:
- A unified
Emailsender for SMTP, EWS or Microsoft Graph. - A database-agnostic email queue system.
- Helpers for attachments and queued processing.
This package is database-agnostic: it defines the queue interface but does not include any persistence implementation. Use a persistence plugin to provide a concrete queue backend:
beecubu/php-foundation-emailer-mongodb— MongoDB backend.beecubu/php-foundation-emailer-sqlite— SQLite backend.
Requirements
- PHP 7.1+
ext-json,ext-curlbeecubu/php-foundation-coresymfony/mailer5.4php-ews/php-ews(via the configured VCS repo)
Installation
composer require beecubu/php-foundation-emailer
# Plus a persistence plugin (choose one):
composer require beecubu/php-foundation-emailer-mongodb
# or
composer require beecubu/php-foundation-emailer-sqlite
Basic SMTP Example
<?php
use Beecubu\Foundation\Emailer\Entities\Email\Email;
use Beecubu\Foundation\Emailer\Entities\EmailConfig\EmailConfig;
use Beecubu\Foundation\Emailer\Entities\EmailConfig\EmailConfigService;
$config = new EmailConfig();
$config->service = EmailConfigService::SMTP;
$config->from = 'no-reply@example.com';
$config->fromName = 'Example App';
$config->server = 'smtp.example.com';
$config->port = 587;
$config->userName = 'smtp-user';
$config->password = 'smtp-pass';
$config->verifyPeer = true;
$mailer = Email::emailWithEmailConfig($config);
$mailer->send(
'owner-1',
'user@example.com',
'User Name',
'Welcome',
'<p>Hello!</p>',
false
);
EWS Example
<?php
use Beecubu\Foundation\Emailer\Entities\Email\Email;
use Beecubu\Foundation\Emailer\Entities\EmailConfig\EmailConfig;
use Beecubu\Foundation\Emailer\Entities\EmailConfig\EmailConfigService;
use Beecubu\Foundation\Emailer\Entities\EmailConfig\EmailEWSVersion;
$config = new EmailConfig();
$config->service = EmailConfigService::EWS;
// You can use either the bare host or the full Exchange endpoint URL.
$config->server = 'https://exchange.example.com/EWS/Exchange.asmx';
$config->userName = 'user@example.com';
$config->password = 'password';
$config->version = EmailEWSVersion::VERSION_2010;
$config->verifyPeer = false;
$mailer = Email::emailWithEmailConfig($config);
$mailer->send(
'owner-1',
'user@example.com',
'User Name',
'EWS Test',
'<p>Sent via EWS</p>',
false
);
The server field accepts any of these formats — all are normalized internally:
exchange.example.comexchange.example.com/EWS/Exchange.asmxhttps://exchange.example.com/EWS/Exchange.asmx
SMTP Options
SMTP supports explicit overrides, but the recommended configuration is to let Symfony resolve them automatically.
<?php
use Beecubu\Foundation\Emailer\Entities\EmailConfig\EmailConfigAuth;
use Beecubu\Foundation\Emailer\Entities\EmailConfig\EmailConfigEncryption;
$config->authMode = EmailConfigAuth::AUTO;
$config->encryption = EmailConfigEncryption::Encryption_Auto;
$config->verifyPeer = true;
Available options:
EmailConfigAuth::AUTO: recommended, delegates authentication negotiation to Symfony.EmailConfigAuth::NULL: disables SMTP credentials. When using this mode,userNameandpasswordare not required.EmailConfigAuth::PLAIN,LOGIN,CRAM_MD5: accepted for configuration compatibility, but the framework now prioritizes Symfony's automatic negotiation for forward compatibility.EmailConfigEncryption::Encryption_Auto: recommended, delegates TLS behavior to Symfony.EmailConfigEncryption::Encryption_TLS: forces SMTP with STARTTLS-style negotiation.EmailConfigEncryption::Encryption_SSL: forces implicit SMTPS.EmailConfigEncryption::Encryption_None: disables TLS at transport construction time.
Microsoft Graph Example
Microsoft Graph is the modern Microsoft API for Office 365 / Exchange Online. Authentication uses OAuth 2.0 client credentials (app-only), which means no user interaction is required — the application authenticates directly with Azure AD using a tenantId, clientId and clientSecret.
1) Register an Azure AD application
- Go to Azure Portal → Azure Active Directory → App registrations → New registration.
- Give it a name (e.g.
MyApp Emailer) and register it. - Go to API permissions → Add a permission → Microsoft Graph → Application permissions.
- Add
Mail.Send. - Click Grant admin consent (required for application permissions).
- Go to Certificates & secrets → New client secret. Copy the secret value immediately.
- Note the Application (client) ID and the Directory (tenant) ID from the Overview page.
2) Configure and send
<?php
use Beecubu\Foundation\Emailer\Entities\Email\Email;
use Beecubu\Foundation\Emailer\Entities\EmailConfig\EmailConfig;
use Beecubu\Foundation\Emailer\Entities\EmailConfig\EmailConfigService;
$config = new EmailConfig();
$config->service = EmailConfigService::GRAPH;
$config->from = 'no-reply@yourdomain.com'; // must be a mailbox in your tenant
$config->fromName = 'Example App';
$config->tenantId = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
$config->clientId = 'yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy';
$config->clientSecret = 'your-client-secret';
$config->verifyPeer = true;
$mailer = Email::emailWithEmailConfig($config);
$mailer->send(
'owner-1',
'user@example.com',
'User Name',
'Graph Test',
'<p>Sent via Microsoft Graph</p>',
false
);
How it works internally
- On the first send,
Graphrequests an access token fromhttps://login.microsoftonline.com/{tenantId}/oauth2/v2.0/tokenusing the client credentials grant. - The token is cached in memory until 60 seconds before its expiry (typically 1 hour), so subsequent sends within the same process reuse it.
- The email is sent via
POST https://graph.microsoft.com/v1.0/users/{from}/sendMail. - On failure, an
EmailGraphSendingErrorexception is thrown with the error message returned by the API.
Note: The
fromaddress must be a valid mailbox within your Azure AD tenant. The registered application needs theMail.Sendapplication permission (not delegated) for this flow to work without a signed-in user.
Verification Model
EmailConfig now separates two different concerns:
sendingVerified: confirms that the technical delivery configuration has passed a functional send test.ownershipVerified: confirms that the mailbox infromhas been validated by a verification token.
Related internal fields:
sendingVerification: hash used to detect delivery-related configuration changes.ownershipVerification: token tied to thefrommailbox ownership.
Behavior:
- Changing SMTP / EWS / Graph technical settings invalidates
sendingVerified. - Changing
frominvalidatesownershipVerified. verifySending()marks the config as delivery-verified.verifyOwnershipToken($token)validates mailbox ownership.
Attachments
use Beecubu\Foundation\Emailer\Entities\Email\EmailAttachment;
$attachments = [
EmailAttachment::create('report.pdf', $pdfBinary, 'application/pdf'),
];
$mailer->send(
'owner-1',
'user@example.com',
'User Name',
'Report',
'<p>Attached.</p>',
false,
$attachments
);
Attachments work the same way across all three transports (SMTP, EWS, Graph).
Queueing Emails
When $queue = true, emails are stored in the configured persistence backend and can be sent later.
1) Initialize persistence
Before queuing any emails, register a persistence backend using the plugin's bootstrap:
// MongoDB
use Beecubu\Foundation\Emailer\MongoDB\RepositoryBootstrap;
RepositoryBootstrap::register();
// or SQLite
use Beecubu\Foundation\Emailer\SQLite\RepositoryBootstrap;
RepositoryBootstrap::register();
2) Queue an email
$mailer->send(
'owner-1',
'user@example.com',
'User Name',
'Queued Email',
'<p>This will be queued.</p>',
true
);
3) Processing the Queue
Extend EmailQueueSender to provide a config for each queued item:
<?php
use Beecubu\Foundation\Emailer\Entities\EmailQueueItem\EmailQueueSender;
use Beecubu\Foundation\Emailer\Entities\EmailQueueItem\EmailQueueItem;
use Beecubu\Foundation\Emailer\Entities\EmailConfig\EmailConfig;
class MyQueueSender extends EmailQueueSender
{
protected static function getEmailConfigByItem(EmailQueueItem $item): ?EmailConfig
{
// Resolve the EmailConfig for this ownerId.
// Return null if it does not exist.
return MyEmailConfigRepo::findByOwnerId($item->ownerId);
}
}
// Silent (default is verbose)
MyQueueSender::execute();
// With console logs
MyQueueSender::execute(verbose: true);
When $verbose is enabled, each email processed prints:
- Before sending: recipient, subject and owner ID.
- On success: confirmation line.
- On error: the error message.
The queue will:
- Fetch the next email in
waitingstate. - Mark it as
sendingto avoid duplicates across concurrent workers. - Send it via the configured transport (SMTP, EWS or Graph).
- Remove it from the queue on success.
- Mark it as
erroron failure and store: error: a short error message.errorDebug: a technical debug log with transport-specific details when available.
Architecture
php-foundation-emailer (core, DB-agnostic)
↑ implemented by
php-foundation-emailer-mongodb (MongoDB plugin)
php-foundation-emailer-sqlite (SQLite plugin)
The core package defines IEmailQueueRepository and EmailQueueProvider. Plugins provide concrete implementations and register them via RepositoryBootstrap::register().
Notes
- For SMTP, this package uses Symfony Mailer.
- For EWS, it uses
php-ews/php-ews(on-premise Exchange). - For Microsoft Graph, it uses the Graph REST API directly via
ext-curl(no extra SDK required). This is the recommended approach for Office 365 / Exchange Online. - On queued failures, SMTP stores Symfony transport debug output, Graph stores endpoint/HTTP/API diagnostic context, and EWS stores request/response metadata in
errorDebug.