trail / mail
A mailer addon for trail
0.4.1
2026-10-23 23:26 UTC
Requires
- php: >=8.4
- ext-openssl: *
- trail/framework: ^0.4.0
Requires (Dev)
None
Suggests
- trail/queue: send mail from a worker with app.mail.queue
Provides
None
Conflicts
None
Replaces
None
README
A mailer addon for trail framework
install
composer require trail/mail
Needs openssl
config
// app.php
'mail' => [
'dsn' => App::env('MAIL_DSN'),
'from' => 'hi@example.com',
'name' => 'Example',
],
# .env
MAIL_DSN="smtps://user%40example.com:password@smtp.example.com:465"
Provider gives a host and a port. Use the scheme that matches the port:
- port 465:
smtps://user:pass@host:465 - port 587:
smtp://user:pass@host:587 @,:, and/in the user or password need url encoding (%40,%3A,%2F)- no dsn? dev mode logs every letter. prod logs a warning
sending
use Trail\Mail\Letters\Letter;
use Trail\Mail\Outbox\Outbox;
public function __construct(private Outbox $outbox) {}
$this->outbox->send(new Letter(
to: 'wat@example.com',
subject: 'me innit',
text: View::make('mail/welcome', ['user' => $user]),
html: View::make('mail/welcome-html', ['user' => $user]), // optional
replyTo: 'oops@example.com', // optional
cc: ['also@example.com'], // optional
bcc: ['shh@example.com'], // optional
headers: ['List-Unsubscribe' => '<https://example.com/bye>'], // optional
));
- one
toper letter.ccandbcctake lists - bcc doesnt show up in messages
- addresses get checked
- subjects and names are safe
headerscan be set if needed at one line of ascii each (yahoo/google thing)- if the server says no it yells
Undeliveredwith what the server said
Sending happens during request, so there might be a delay
from templates
Letter::write renders a template which sends plain text bits to $draft
$this->outbox->send(Letter::write('wat@example.com', 'mail/welcome', ['user' => $user]));
$this->outbox->send(Letter::write('wat@example.com', 'mail/contact', $data, replyTo: 'them@example.com'));
<?php $draft->fill([
'subject' => 'me innit',
'greeting' => sprintf('hi %s,', $user->name()),
'message' => "welcome aboard\n\nclick link to finish",
'link' => $url,
'disclaimer' => 'not you? ignore it',
'footer' => 'sent by example.com',
]);
subject,greeting,message,link,disclaimer, andfootercan be used- text part is that order. blank lines between elements, footer live below
-- - twig:
{% do draft.fill({ subject: subject, ... }) %}
queue it
Add trail/queue and you can use a worker instead
'mail' => [
'dsn' => App::env('MAIL_DSN'),
'from' => 'hi@example.com',
'queue' => true,
],
send()pushes a job,- failed sends get retried by queue,
Undeliveredends up inqueue::failed - letters sit in
queue_jobsuntil sent - no worker kills mail.
app.queue.runNowin config will send right away. good for local dev
your own outbox
Bind anything that implements Outbox in a site addon
$box->bind(Outbox::class, MyOutbox::class);
For testing you have Pigeonhole, which keeps letters in $letters instead of sending them
$box->bind(Outbox::class, Pigeonhole::class);
cli
trail/trail mail::test stuff@example.com
not done yet
- attachments
- letter types (
transactionalorbroadcast)