hydrakit / mail
Outgoing mail behind one transport contract: SMTP for delivery, log and array transports so tests and local development never send.
Requires
- php: >=8.4
- hydrakit/core: ^0.31
- psr/clock: ^1.0
- psr/event-dispatcher: ^1.0
- psr/log: ^3.0
Requires (Dev)
- hydrakit/event: ^0.31
- hydrakit/log: ^0.31
- phpunit/phpunit: ^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main / 0.31.x-dev
- v0.31.2
- v0.31.1
- v0.31.0
- v0.30.0
- v0.29.0
- v0.28.0
- v0.27.0
- v0.26.0
- v0.25.0
- v0.24.0
- v0.23.0
- v0.22.1
- v0.22.0
- v0.21.0
- v0.20.0
- v0.19.0
- v0.18.0
- v0.17.0
- v0.16.0
- v0.15.0
- v0.14.0
- v0.13.0
- v0.12.0
- v0.11.0
- v0.10.2
- v0.10.1
- v0.10.0
- v0.9.17
- v0.9.16
- v0.9.15
- v0.9.14
- v0.9.13
- v0.9.12
- v0.9.11
- v0.9.10
- v0.9.9
- v0.9.8
- v0.9.7
- v0.9.6
- v0.9.5
- v0.9.4
- v0.9.3
- v0.9.2
- v0.9.1
- v0.9.0
- v0.8.1
- v0.8.0
- v0.7.2
- v0.7.1
- v0.7.0
- v0.6.3
- v0.6.2
- v0.6.1
- v0.6.0
- v0.5.8
- v0.5.7
This package is auto-updated.
Last update: 2026-10-09 13:58:40 UTC
README
Part of the Hydra PHP framework. Documentation: hydra.williamhleucka.com/docs.
Read-only mirror.
hydrakit/mailis developed in hydra-foundation/hydra underpackages/mail, and republished here on every push. A commit pushed to this repository is overwritten by the next one; issues are disabled for that reason, and a pull request opened here cannot be merged. Both belong upstream.
Outgoing mail behind one transport contract. SmtpTransport delivers; LogTransport
writes each message to the log with its links intact; ArrayTransport keeps them
in memory. Only the first sends anything, so tests and local development never do.
$mailer->send(Message::make() ->to('ada@example.com', 'Ada') ->subject('Reset your password') ->text("Follow this link: {$url}") ->html("<p><a href=\"{$url}\">Reset your password</a></p>"));
A message with no sender gets MAIL_FROM_ADDRESS. Every recipient must be
accepted before the message is sent, so a refused address fails the whole send
rather than delivering to some recipients. TLS is never skipped: with
MAIL_ENCRYPTION=tls a server that does not offer STARTTLS is an error, and
credentials are refused outright with MAIL_ENCRYPTION=none.
| Setting | Default | |
|---|---|---|
MAIL_TRANSPORT |
smtp |
smtp, log or array |
MAIL_HOST |
required for smtp |
|
MAIL_PORT |
587 |
|
MAIL_ENCRYPTION |
tls |
tls (STARTTLS), ssl (implicit TLS) or none |
MAIL_USERNAME, MAIL_PASSWORD |
AUTH PLAIN, or LOGIN when PLAIN is not offered | |
MAIL_FROM_ADDRESS, MAIL_FROM_NAME |
the default sender | |
MAIL_TIMEOUT |
10 |
seconds, for the connection and each reply |
MAIL_EHLO_DOMAIN |
localhost |
the name this client greets the server with |
When the app binds a PSR-14 EventDispatcherInterface, every message a
transport accepts is announced as Events\MessageSent, carrying the message as
sent (sender filled in) and the transport's name. A send that throws announces
nothing. The package stores nothing; an app that wants a log of sent mail
listens:
$listeners->listen(MessageSent::class, function (MessageSent $sent): void { // $sent->message->getTo(), ->getSubject(), $sent->transport … });
A listener that throws reaches the caller of send() after the mail went out,
so one that records should catch and log its own failures. FakeMailer
dispatches nothing.
In tests, register Testing\FakeMailServiceProvider after MailServiceProvider
and assert on the FakeMailer it binds:
$mail = $container->get(FakeMailer::class); $mail->assertSentTo('ada@example.com'); $mail->assertSent(fn (Message $m) => $m->getSubject() === 'Reset your password', times: 1);
Not yet: attachments, custom headers, and SMTPUTF8 (addresses are ASCII; names and subjects may be anything).