Sending Emails
Native SMTP implementation built from scratch with Laravel-inspired Mailable classes
Overview
Larafony's Mail component provides:
- Native SMTP - RFC 5321 compliant implementation from scratch
- Mailable Classes - Laravel-inspired email composition
- Blade Templates - Use views for email content
- Interface-Based - Easy to swap transports via TransportContract
- Database Logging - Track sent emails with MailHistoryLogger
- DSN Configuration - Connection strings with smart defaults
- Framework Integration - Reuses UriManager, Stream, ViewManager
Quick Start
1. Configuration
use Larafony\Framework\Mail\MailerFactory;
// From DSN with smart defaults
$mailer = MailerFactory::fromDsn('smtp://user:pass@smtp.example.com:587');
// MailHog for local development
$mailer = MailerFactory::createMailHogMailer('localhost', 1025);
docker run -p 1025:1025 -p 8025:8025 mailhog/mailhog
2. Create a Mailable Class
namespace App\Mail;
use Larafony\Framework\Mail\Mailable;
use Larafony\Framework\Mail\Envelope;
use Larafony\Framework\Mail\Content;
use Larafony\Framework\Mail\Address;
class WelcomeEmail extends Mailable
{
public function __construct(
private readonly string $userName,
private readonly string $userEmail
) {}
protected function envelope(): Envelope
{
return (new Envelope())
->from(new Address('noreply@example.com', 'Larafony'))
->to(new Address($this->userEmail))
->subject('Welcome to Larafony!');
}
protected function content(): Content
{
return new Content(
view: 'emails.welcome',
data: ['userName' => $this->userName]
);
}
}
3. Create Email View
Create resources/views/emails/welcome.blade.php:
@component('components.Layout', ['title' => 'Welcome'])
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 40px; text-align: center;">
<h1 style="color: white; margin: 0;">Welcome to Larafony!</h1>
</div>
<div style="padding: 40px; background: white;">
<p style="font-size: 18px;">
Hello <strong>{{ $userName }}</strong>,
</p>
<p style="font-size: 16px; line-height: 1.6;">
Thank you for joining Larafony! We're excited to have you on board.
</p>
<div style="text-align: center; margin: 30px 0;">
<a href="https://github.com/larafony/framework"
style="background: #667eea; color: white; padding: 12px 30px;
text-decoration: none; border-radius: 6px;">
Get Started
</a>
</div>
</div>
</div>
@endcomponent
4. Send the Email
$mailer->send(new WelcomeEmail('John Doe', 'john@example.com'));
DSN Configuration
DSN format: smtp://[username:password@]host[:port]
// Basic SMTP (port 25)
$mailer = MailerFactory::fromDsn('smtp://localhost');
// With authentication (port 587 for TLS by default)
$mailer = MailerFactory::fromDsn('smtp://user:pass@smtp.gmail.com:587');
// SSL (port 465)
$mailer = MailerFactory::fromDsn('smtps://user:pass@smtp.gmail.com:465');
// Explicit TLS
$mailer = MailerFactory::fromDsn('smtp+tls://user:pass@smtp.example.com:587');
Smart Port Defaults
- Port 25 - Plain SMTP (no encryption)
- Port 587 - SMTP with TLS/STARTTLS
- Port 465 - SMTP with SSL (smtps://)
Advanced Usage
Multiple Recipients
protected function envelope(): Envelope
{
return (new Envelope())
->from(new Address('noreply@example.com', 'Larafony'))
->to(new Address('user1@example.com'))
->to(new Address('user2@example.com'))
->cc(new Address('manager@example.com'))
->bcc(new Address('admin@example.com'))
->replyTo(new Address('support@example.com'))
->subject('Team Update');
}
Email Logging
Track sent emails in the database:
use Larafony\Framework\Mail\MailHistoryLogger;
use Larafony\Framework\Mail\Mailer;
$logger = new MailHistoryLogger();
$mailer = new Mailer($transport, $logger);
// Emails are automatically logged when sent
$mailer->send($mailable);
Create the mail_log table:
php bin/larafony table:mail-log
php bin/larafony migrate
SMTP Protocol Details
Implemented Commands
- EHLO - Extended hello, establishes connection
- AUTH LOGIN - Base64-encoded authentication
- MAIL FROM - Specifies sender address
- RCPT TO - Specifies recipient addresses (to, cc, bcc)
- DATA - Begins message content
- QUIT - Closes connection
Response Codes
- 2xx - Success (250 OK, 220 Ready)
- 3xx - Intermediate (354 Start mail input)
- 4xx - Transient failure (421 Service not available)
- 5xx - Permanent failure (550 Mailbox unavailable)
Multi-line Response Handling
SMTP responses can span multiple lines. The implementation detects the last line by checking character at index 3:
250-mail.example.com
250-SIZE 52428800
250-8BITMIME
250 HELP
^ Space indicates this is the final line
PHP 8.5 Features
Asymmetric Visibility
Email and Envelope use private(set) for immutability:
final class Email
{
public function __construct(
public private(set) ?Address $from = null,
public private(set) array $to = [],
public private(set) ?string $subject = null,
) {}
// Immutable API using clone()
public function from(Address $address): self
{
return clone($this, ['from' => $address]);
}
public function to(Address $address): self
{
return clone($this, ['to' => [...$this->to, $address]]);
}
}
Property Hooks
Smart defaults with property hooks:
final class MailEncryption
{
public bool $isSsl {
get => $this->value === 'ssl';
}
public bool $isTls {
get => $this->value === 'tls';
}
private function __construct(
public private(set) string $value
) {}
}
private(set) cannot use reference-based operations like array_walk(). According to RFC Asymmetric Visibility v2, obtaining a reference follows set visibility, not get visibility. Use foreach for read-only iteration.
Architecture
Value Objects
- Address - Email address with optional name
- MailPort - Smart port selection based on encryption
- MailEncryption - SSL, TLS, or none
- MailUserInfo - Parses username:password from DSN
- SmtpCommand - SMTP commands with validation
- SmtpResponse - SMTP response parsing and validation
Contracts (Interfaces)
interface TransportContract
{
public function send(Email $message): void;
}
interface MailerContract
{
public function send(Mailable $mailable): void;
}
interface MailHistoryLoggerContract
{
public function log(Email $message): void;
}
Framework Integration
- UriManager - DSN parsing with scheme detection
- Stream - Socket I/O from HTTP module
- ViewManager - Email template rendering
- Application Container - Dependency injection
- DBAL Models - Email logging with ORM
Testing
Using MailHog
# Start MailHog with Docker
docker run -d -p 1025:1025 -p 8025:8025 mailhog/mailhog
# View sent emails at http://localhost:8025
use Larafony\Framework\Mail\MailerFactory;
$mailer = MailerFactory::createMailHogMailer();
$mailer->send(new WelcomeEmail('Test User', 'test@example.com'));
// Check http://localhost:8025 to see the email
Future Enhancements
After PHP 8.5 GA release and base implementation completion:
- Symfony Mailer Integration - Add
symfony/maileras optional transport - Additional Transports - AWS SES, SendGrid, Mailgun drivers
- Attachments - File attachment support
- Multipart - HTML/text multipart messages
- Inline Images - Embedded image support
- Queue Integration - Async email sending
- Testing Utilities - Fake mailer for unit tests
Resources
- RFC 5321 - Simple Mail Transfer Protocol
- RFC 2045 - MIME Format
- PHP RFC: Property Hooks
- PHP RFC: Asymmetric Visibility v2
- MailHog - Email testing tool
Learn More
This native SMTP implementation is explained in detail with RFC compliance, PHP 8.5 features, and production-ready patterns at masterphp.eu
Coming Soon: Symfony Mailer integration will be added as an optional transport after PHP 8.5 GA, giving you the choice between native implementation and battle-tested external solutions.
View on Packagist View on GitHub