FileTransport.php

PHP

Path: src/Mail/FileTransport.php

<?php

namespace mini\Mail;

/**
 * File-based mail transport for development and testing
 *
 * Logs complete emails (headers + body) to a file instead of sending them.
 * Useful for development when you don't have SMTP configured, and for
 * testing email functionality without sending real emails.
 *
 * Usage:
 *   $transport = new FileTransport('data/mail.log');
 *   $mailer = new Mailer($transport);
 *   $mailer->send($email);
 *
 *   // Then check data/mail.log for the email content
 */
final class FileTransport implements MailTransportInterface
{
    private string $logPath;

    /**
     * @param string $logPath Path to the log file (relative paths are resolved from getcwd())
     */
    public function __construct(string $logPath = 'data/mail.log')
    {
        $this->logPath = $logPath;
    }

    /**
     * Log an email to the file
     *
     * @param EmailInterface $email The email to log
     * @param string $sender Envelope sender (MAIL FROM address)
     * @param array<string> $recipients Envelope recipients (RCPT TO addresses)
     * @throws MailTransportException on failure
     */
    public function send(EmailInterface $email, string $sender, array $recipients): void
    {
        // Resolve relative path
        $path = $this->logPath;
        if (!str_starts_with($path, '/')) {
            $path = getcwd() . '/' . $path;
        }

        // Ensure directory exists
        $dir = dirname($path);
        if (!is_dir($dir)) {
            if (!mkdir($dir, 0755, true)) {
                throw new MailTransportException("Failed to create directory: {$dir}");
            }
        }

        // Build log entry
        $timestamp = date('Y-m-d H:i:s');
        $separator = str_repeat('=', 78);

        $entry = "{$separator}\n";
        $entry .= "LOGGED EMAIL - {$timestamp}\n";
        $entry .= "{$separator}\n";
        $entry .= "Envelope-From: {$sender}\n";
        $entry .= "Envelope-To: " . implode(', ', $recipients) . "\n";
        $entry .= "{$separator}\n";

        // Get complete email content (headers + body)
        $entry .= (string) $email;
        $entry .= "\n\n";

        // Append to file
        if (file_put_contents($path, $entry, FILE_APPEND | LOCK_EX) === false) {
            throw new MailTransportException("Failed to write to mail log: {$path}");
        }
    }

    /**
     * Get the configured log path
     */
    public function getLogPath(): string
    {
        return $this->logPath;
    }
}