beecubu/php-foundation-emailer

PHP Object Foundation Framework Emailer library for SMTP, EWS and Microsoft Graph

Maintainers

Package info

bitbucket.org/beecubu/php-foundation-emailer

pkg:composer/beecubu/php-foundation-emailer

Statistics

Installs: 49

Dependents: 2

Suggesters: 0

v2.3.0 2026-03-17 13:45 UTC

This package is auto-updated.

Last update: 2026-03-17 13:45:27 UTC


README

PHP Object Foundation Emailer with SMTP, Microsoft Exchange Web Services (EWS) and Microsoft Graph API support. It includes:

  • A unified Email sender 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:

Requirements

  • PHP 7.1+
  • ext-json, ext-curl
  • beecubu/php-foundation-core
  • symfony/mailer 5.4
  • php-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.com
  • exchange.example.com/EWS/Exchange.asmx
  • https://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, userName and password are 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

  1. Go to Azure PortalAzure Active DirectoryApp registrationsNew registration.
  2. Give it a name (e.g. MyApp Emailer) and register it.
  3. Go to API permissionsAdd a permissionMicrosoft GraphApplication permissions.
  4. Add Mail.Send.
  5. Click Grant admin consent (required for application permissions).
  6. Go to Certificates & secretsNew client secret. Copy the secret value immediately.
  7. 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

  1. On the first send, Graph requests an access token from https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token using the client credentials grant.
  2. The token is cached in memory until 60 seconds before its expiry (typically 1 hour), so subsequent sends within the same process reuse it.
  3. The email is sent via POST https://graph.microsoft.com/v1.0/users/{from}/sendMail.
  4. On failure, an EmailGraphSendingError exception is thrown with the error message returned by the API.

Note: The from address must be a valid mailbox within your Azure AD tenant. The registered application needs the Mail.Send application 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 in from has been validated by a verification token.

Related internal fields:

  • sendingVerification: hash used to detect delivery-related configuration changes.
  • ownershipVerification: token tied to the from mailbox ownership.

Behavior:

  • Changing SMTP / EWS / Graph technical settings invalidates sendingVerified.
  • Changing from invalidates ownershipVerified.
  • 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 waiting state.
  • Mark it as sending to 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 error on 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.