PHPackages                             phpdot/outbox - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Mail &amp; Notifications](/categories/mail)
4. /
5. phpdot/outbox

ActiveLibrary[Mail &amp; Notifications](/categories/mail)

phpdot/outbox
=============

SMTP proxy server for PHP. Accept emails, store them, deliver via AWS SES or any provider.

v1.0.0(1mo ago)00MITPHPPHP &gt;=8.2

Since Mar 27Pushed 1mo agoCompare

[ Source](https://github.com/phpdot/outbox)[ Packagist](https://packagist.org/packages/phpdot/outbox)[ RSS](/packages/phpdot-outbox/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (3)Versions (2)Used By (0)

phpdot/outbox
=============

[](#phpdotoutbox)

SMTP proxy server for PHP. Accept emails via SMTP, store them, deliver via AWS SES or any provider.

Install
-------

[](#install)

```
composer require phpdot/outbox

```

Requires PHP 8.2+. Zero runtime dependencies.

Quick Start
-----------

[](#quick-start)

```
use PHPdot\Mail\Outbox\SmtpHandler;
use PHPdot\Mail\Outbox\Server\StreamServer;
use PHPdot\Mail\Outbox\Server\Event\AuthEvent;
use PHPdot\Mail\Outbox\Server\Event\MessageEvent;

$handler = new SmtpHandler();

$handler->onAuth(function (AuthEvent $event, $ctx): void {
    if ($event->credentials->username === 'api' && $event->credentials->password === 'secret') {
        $event->accept();
    } else {
        $event->reject('Invalid credentials');
    }
});

$handler->onMessage(function (MessageEvent $event, $ctx): void {
    file_put_contents("/var/mail/queue/{$event->id}.eml", $event->message);
    $event->accept();
});

$server = new StreamServer($handler, port: 2525);
$server->start();
```

Server
------

[](#server)

The library ships with `StreamServer` — a concurrent TCP server using `stream_select()`. No extensions needed.

```
$server = new StreamServer(
    handler: $handler,
    host: '0.0.0.0',
    port: 2525,
    hostname: 'mail.example.com',
    tlsRequired: false,
    authRequired: true,
);

// Optional: enable STARTTLS
$server->enableTls('/path/to/cert.pem', '/path/to/key.pem');

$server->start();
```

For Swoole, Workerman, or ReactPHP — implement `ServerInterface` or use `ServerConnection` directly:

```
use PHPdot\Mail\Outbox\Connection\ServerConnection;

// On each TCP connection:
$conn = new ServerConnection($handler, hostname: 'mail.example.com');
$greeting = $conn->greeting();       // send to client
$responses = $conn->onData($bytes);  // feed client bytes, get SMTP responses
$conn->needsTlsUpgrade();            // check if TLS upgrade needed
$conn->tlsUpgraded();                // notify TLS complete
$conn->isClosing();                  // check if QUIT received
```

Handlers
--------

[](#handlers)

Every handler receives a typed event and the connection context. Call `$event->accept()` or `$event->reject()` to control the SMTP response.

```
use PHPdot\Mail\Outbox\Server\Event\ConnectEvent;
use PHPdot\Mail\Outbox\Server\Event\AuthEvent;
use PHPdot\Mail\Outbox\Server\Event\MailFromEvent;
use PHPdot\Mail\Outbox\Server\Event\RcptToEvent;
use PHPdot\Mail\Outbox\Server\Event\MessageEvent;

$handler->onConnect(function (ConnectEvent $event, $ctx): void {
    // $event->clientIp
    $event->accept();
});

$handler->onAuth(function (AuthEvent $event, $ctx): void {
    // $event->credentials->username
    // $event->credentials->password
    // $event->credentials->mechanism (PLAIN, LOGIN, XOAUTH2)
    $event->accept();
});

$handler->onMailFrom(function (MailFromEvent $event, $ctx): void {
    // $event->sender
    // $event->size
    // $event->bodyType
    $event->accept();
});

$handler->onRcptTo(function (RcptToEvent $event, $ctx): void {
    // $event->recipient
    $event->accept();
});

$handler->onMessage(function (MessageEvent $event, $ctx): void {
    // $event->id
    // $event->sender
    // $event->recipients
    // $event->message (raw .eml)
    // $event->size
    $event->accept();
});

$handler->onQueued(function (MessageEvent $event, $ctx): void {
    // Fire-and-forget after message is accepted
});
```

Storage
-------

[](#storage)

```
use PHPdot\Mail\Outbox\Storage\FilesystemStorage;
use PHPdot\Mail\Outbox\DataType\DTO\MessageEnvelope;
use PHPdot\Mail\Outbox\DataType\Enum\QueueState;

$storage = new FilesystemStorage('/var/mail/outbox');

$handler->onMessage(function (MessageEvent $event, $ctx) use ($storage): void {
    $envelope = new MessageEnvelope(
        id: $event->id,
        sender: $event->sender,
        recipients: $event->recipients,
        size: $event->size,
        state: QueueState::Pending,
        authUser: $ctx->user() ?? '',
        clientIp: $ctx->clientIp,
        receivedAt: new \DateTimeImmutable(),
    );

    $storage->store($envelope, $event->message);
    $event->accept();
});
```

For S3, Redis, or database storage — implement `StorageInterface`.

Delivery
--------

[](#delivery)

```
use PHPdot\Mail\Outbox\Delivery\DeliveryWorker;
use PHPdot\Mail\Outbox\Delivery\SesTransport;

$transport = new SesTransport($yourSesClient);
$worker = new DeliveryWorker($storage, $transport);

$worker->onDelivered(function (string $id, string $providerMsgId, string $transport): void {
    echo "Delivered $id via $transport ($providerMsgId)\n";
});

$worker->onFailed(function (string $id, string $error, int $attempts): void {
    echo "Failed $id after $attempts attempts: $error\n";
});

// Process a batch
$worker->processBatch(10);

// Or run continuously
$worker->run();
```

### SMTP relay transport

[](#smtp-relay-transport)

```
use PHPdot\Mail\Outbox\Delivery\SmtpRelayTransport;

$transport = new SmtpRelayTransport(
    host: 'smtp.postmarkapp.com',
    port: 587,
    encryption: 'tls',
    username: 'your-api-token',
    password: 'your-api-token',
);

$worker = new DeliveryWorker($storage, $transport);
$worker->run();
```

What's Covered
--------------

[](#whats-covered)

### SMTP Server (RFC 5321)

[](#smtp-server-rfc-5321)

- EHLO / HELO handshake
- STARTTLS upgrade (RFC 3207)
- AUTH PLAIN, LOGIN, XOAUTH2 (RFC 4954, RFC 7628)
- MAIL FROM with SIZE check (RFC 1870)
- RCPT TO with multiple recipients
- DATA with dot-stuffing
- RSET, NOOP, QUIT
- PIPELINING support (RFC 2920)
- 8BITMIME support (RFC 6152)

### Security

[](#security)

- Require TLS before AUTH
- Require AUTH before sending
- Max message size enforcement
- Max recipients per message
- Max bad commands before disconnect
- Configurable: TLS optional, AUTH optional

### Storage

[](#storage-1)

- Pluggable storage interface
- Filesystem storage (works out of the box)
- Queue states: pending → sending → sent / failed
- Retry tracking with metadata

### Delivery

[](#delivery-1)

- Pluggable transport interface
- AWS SES transport (via SesClientInterface)
- SMTP relay transport (any SMTP provider)
- Exponential backoff retry (30s → 2h)
- Stuck message recovery
- Event hooks: onDelivered, onFailed, onRetry

License
-------

[](#license)

MIT

###  Health Score

37

—

LowBetter than 83% of packages

Maintenance90

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

46d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/62e82421bda4b5d6ba9a47ba6d88caca060dcd0d1a2862f351f3a97657385db0?d=identicon)[phpdot](/maintainers/phpdot)

---

Top Contributors

[![phpdot](https://avatars.githubusercontent.com/u/252500?v=4)](https://github.com/phpdot "phpdot (2 commits)")

---

Tags

proxymailemailsmtpsesRelayoutboxRFC5321

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/phpdot-outbox/health.svg)

```
[![Health](https://phpackages.com/badges/phpdot-outbox/health.svg)](https://phpackages.com/packages/phpdot-outbox)
```

###  Alternatives

[mlocati/spf-lib

Parse, build and validate SPF (Sender Policy Framework) DNS records

67867.9k2](/packages/mlocati-spf-lib)[pear/net_smtp

An implementation of the SMTP protocol

263.0M16](/packages/pear-net-smtp)[thefox/smtpd

SMTP server (library) written in pure PHP.

1302.4k1](/packages/thefox-smtpd)[ikkez/f3-mailer

SMTP plugin wrapper for PHP Fat-Free Framework

198.7k2](/packages/ikkez-f3-mailer)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
