Sending Emails

Native SMTP implementation built from scratch with Laravel-inspired Mailable classes

Zero Dependencies: Complete RFC 5321 compliant SMTP implementation without external libraries. Symfony Mailer support coming after PHP 8.5 GA.

Overview

Larafony's Mail component provides:

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);
Development Tip: Use MailHog to test emails locally without sending real messages. Install with Docker: 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

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

Response Codes

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
RFC Compliance: Our implementation follows RFC 5321 specification for SMTP protocol.

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
    ) {}
}
Important Caveat: Properties with 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

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

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:

Resources

Learn More

This native SMTP implementation is explained in detail with RFC compliance, PHP 8.5 features, and production-ready patterns at masterphp.eu

Zero Dependencies: Unlike other frameworks that rely on Symfony Mailer or SwiftMailer, Larafony implements SMTP from scratch using only PSR standards and PHP 8.5. This demonstrates complete framework transparency - you can read and understand every line of the mail system without diving into external libraries.

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