maatify / email-delivery
Standalone email delivery module for rendering, queueing and sending emails.
Requires
- php: ^8.2
- ext-json: *
- ext-pdo: *
- maatify/crypto: ^1.0
- phpmailer/phpmailer: ^6.9
- psr/log: ^3.0
- twig/twig: ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.94
- phpstan/phpstan: ^1.12
- phpunit/phpunit: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Maatify Email Delivery
A framework-independent PHP library for an encrypted email queue, Twig HTML rendering and SMTP transport through PHPMailer. Applications enqueue work; a separately run worker attempts delivery.
Release artifact target: 1.2.0. Publication state: published.
Version 1.2.0 was published on
2026-11-06 UTC and verified as an exact Composer dist installation. Its immutable qualified source is
8e0fc1e2995fb52d1e0e5e299eea2203ccd56c2d.
The tagged artifact retains its historically valid pre-publication qualification snapshot. This live repository records later publication facts; it does not replace the tag or change the distributed artifact.
Start here
- Usage Guide and root examples
- Package Reference — canonical supported contract and complete API inventory
- Documentation map
- Idempotency and receipt reference — the complete keyed API contract
- Synthetic no-send recording — encrypted inert storage and bounded maintenance
- Install or upgrade the schema — fresh and existing installations differ
- Prepare the next release
- Examples, how-to guides, architecture book
- Security policy and security architecture description
Choose the enqueue contract
| Requirement | API | Result |
|---|---|---|
| Each call intentionally creates another job | EmailQueueWriterInterface::enqueue() / PdoEmailQueueWriter |
void; no request deduplication |
| Retrying one identified request must not create another job | IdempotentEmailQueueWriterInterface::enqueueOnce() / PdoIdempotentEmailQueue |
A committed receipt; different content on the same key conflicts |
| Record an inert request without any email job | SyntheticEmailRecorderInterface::record() / PdoSyntheticEmailStore |
Distinct synthetic record; never a delivery receipt |
| Observe an accepted keyed request | EmailEnqueueReceiptReaderInterface::find(namespace, key) |
Scoped receipt or null; no recipient/context returned |
Keyed enqueue atomically stores a job and its durable receipt. Repeating the same namespace/key and logical content returns the same receipt/job identity. It does not guarantee exactly-once SMTP, mailbox arrival, or automatic recovery of a worker interrupted after claiming a job.
Public Runtime API
| Capability | Supported entry points |
|---|---|
| Per-call queue insertion | EmailQueueWriterInterface, PdoEmailQueueWriter |
| Keyed acceptance and scoped observation | IdempotentEmailQueueWriterInterface, EmailEnqueueReceiptReaderInterface, PdoIdempotentEmailQueue |
| Inert recording and trusted cleanup | SyntheticEmailRecorderInterface, SyntheticEmailMaintenanceInterface, PdoSyntheticEmailStore |
| Rendering | EmailRendererInterface, TwigEmailRenderer, payload/rendered DTOs |
| Explicit SMTP call | EmailTransportInterface, SmtpEmailTransport, EmailTransportConfigDTO |
| One processing batch | EmailQueueWorker |
The Package Reference owns the complete supported inventory, DTO fields, enum values, exceptions and behavioral boundaries. Internal helpers are not consumer API.
Installation and dependencies
composer require maatify/email-delivery:1.2.0
This exact version is available through Packagist. To follow the current Stable line, use
composer require maatify/email-delivery.
The package requires PHP 8.2+, JSON and PDO, plus maatify/crypto, Twig, PHPMailer and PSR-3 logging.
The production keyed and synthetic storage paths use MySQL/MariaDB with pdo_mysql and the package-owned
SQL files. SQLite is used by legacy tests, not supported as keyed production storage.
Supply your application PDO, crypto provider/context provider, renderer, transport and logger through DI.
Monolog is not a package dependency.
Apply the schema installation/upgrade before adopting the keyed API. Do not run all numbered SQL files on a fresh installation: the binding upgrade is for an existing legacy table.
Synthetic recording uses an optional independent table and separate crypto domains. Its retention is explicitly configured by the consumer; cleanup is scheduled externally. It provides no constant-time or HTTP security guarantee. See the full contract.
Quick Usage
This function defines application wiring; including it does not connect or send mail. Its dependencies must already be configured. See the complete examples for construction, explicit scheduling, receipt lookup and worker composition.
use Maatify\EmailDelivery\Queue\DTO\EmailEnqueueReceiptDTO; use Maatify\EmailDelivery\Queue\DTO\EmailEnqueueRequestDTO; use Maatify\EmailDelivery\Queue\DTO\EmailQueuePayloadDTO; use Maatify\EmailDelivery\Queue\IdempotentEmailQueueWriterInterface; function queueWelcome( IdempotentEmailQueueWriterInterface $queue, string $authenticatedNamespace, string $stableRequestKey, string $recipient, string $name, ): EmailEnqueueReceiptDTO { return $queue->enqueueOnce(new EmailEnqueueRequestDTO( consumerNamespace: $authenticatedNamespace, requestKey: $stableRequestKey, entityType: 'user', entityId: null, recipientEmail: $recipient, payload: new EmailQueuePayloadDTO(['name' => $name], 'welcome', 'en'), senderType: 1, )); }
Persist the key and intended content in the caller; do not generate a new key on every retry. Namespace is a scope, not authentication. Derive it from the authenticated caller and keep PII/secrets out of keys. The reference defines canonical content, validation, transaction errors, indefinite receipt/key retention and recovery with the same key on a fresh connection.
Worker and delivery boundaries
EmailQueueWorker::processBatch() claims a bounded pending batch under row locks, commits the claim,
then decrypts/renders and calls the transport outside that transaction. Run it from your application's
supervised process using the worker guide.
Keyed jobs use database UTC for due/retry selection, retry deadlines and sent_at. NULL-token legacy
jobs retain local database NOW() and PHP-local retry calculation. The worker supports pre-binding
legacy tables and does not mutate the session timezone. Clock and failure details.
Development verification
composer validate --strict
composer test
composer analyse -- --memory-limit=512M
composer check:docs
composer check:examples
composer check:artifact
Without DB configuration, real-DB tests skip. CI is configured for PHP 8.2/8.3/8.4/8.5 with required MySQL
integration tests and an additional no-DB skip check. For a disposable dedicated database ending in
_test, set EMAIL_DELIVERY_IT_DSN, EMAIL_DELIVERY_IT_USER, EMAIL_DELIVERY_IT_PASS and
EMAIL_DELIVERY_IT_REQUIRED=1. Tests own/drop their queue tables; never target an application database.
See the release checklist for evidence and documentation checks.
License
This library is licensed under the MIT License. Vulnerability reports follow SECURITY.md.
Author
Engineered by Mohamed Abdulalim (@megyptm)
Backend Lead & Technical Architect
https://www.maatify.dev
Built with ❤️ by Maatify.dev — Unified Ecosystem for Modern PHP Libraries