Mail

Introduction

Slenix includes a built-in mail class for sending transactional emails without any external dependencies. It supports both PHP's native mail() function and direct SMTP connections with TLS/SSL encryption — ideal for welcome emails, password resets, invoice delivery, and bulk notifications.

The class is available at Slenix\Supports\Mail\Mail and is configured automatically from your .env file.

php
use Slenix\Supports\Mail\Mail;

(new Mail())
    ->from('no-reply@example.com', 'My App')
    ->to('user@example.com')
    ->subject('Welcome!')
    ->message('<h1>Your account is ready.</h1>', true)
    ->send();

Configuration

Set your mail credentials in .env. The Mail class reads these values automatically on instantiation:

dotenv
EMAIL_METHOD=smtp

SMTP_HOST=smtp.mailtrap.io
SMTP_PORT=2525
SMTP_USERNAME=your_username
SMTP_PASSWORD=your_password
SMTP_ENCRYPTION=tls        # tls | ssl | none
SMTP_AUTH=true
SMTP_TIMEOUT=30

SMTP Providers

Common provider settings:

ProviderHostPortEncryption
Mailtrap (dev)sandbox.smtp.mailtrap.io2525tls
Gmailsmtp.gmail.com587tls
Outlook / Hotmailsmtp-mail.outlook.com587tls
SendGridsmtp.sendgrid.net587tls
Mailgunsmtp.mailgun.org587tls

Gmail: You must use an App Password instead of your normal account password. Enable 2-Step Verification first, then generate an app-specific password.

Manual Configuration

You can also configure SMTP programmatically using the smtp() method, which overrides .env values:

php
(new Mail())
    ->smtp([
        'host'       => 'smtp.gmail.com',
        'port'       => 587,
        'username'   => 'you@gmail.com',
        'password'   => 'your-app-password',
        'encryption' => 'tls',
        'auth'       => true,
        'timeout'    => 30,
    ])
    ->from('you@gmail.com', 'My App')
    ->to('user@example.com')
    ->subject('Hello')
    ->message('Hello from Slenix.')
    ->send();

Configuration Options

OptionDefaultDescription
hostlocalhostSMTP server hostname
port587SMTP server port
username''SMTP username
password''SMTP password
encryptiontlsEncryption: tls, ssl, or none
authtrueWhether to authenticate
timeout30Connection timeout in seconds

Sending Methods

Slenix Mail supports two sending backends, configured via method():

php
// PHP's native mail() — suitable for simple local setups
(new Mail())->method('mail')->...->send();

// SMTP — recommended for production
(new Mail())->method('smtp')->...->send();

When smtp() is called directly, the method is automatically switched to SMTP. For production environments, always prefer SMTP with authentication over mail().


Building and Sending Emails

Basic Email

php
use Slenix\Supports\Mail\Mail;

$sent = (new Mail())
    ->from('no-reply@example.com', 'Example App')
    ->to('user@example.com')
    ->subject('Welcome to Example App')
    ->message('Thank you for signing up.')
    ->send();

if (!$sent) {
    // Handle failure
}

HTML Emails

Pass true as the second argument to message() to send HTML:

php
$html = '<h1>Welcome, ' . $user->name . '!</h1>'
      . '<p>Your account has been created successfully.</p>'
      . '<p><a href="https://example.com/login">Log in now</a></p>';

(new Mail())
    ->from('no-reply@example.com', 'Example App')
    ->to($user->email)
    ->subject('Welcome!')
    ->message($html, true)
    ->send();

Emails With Attachments

Call attach() once per file. The file must exist on the server. Slenix encodes attachments as Base64 and sends them as multipart/mixed:

php
(new Mail())
    ->from('billing@example.com', 'Example Billing')
    ->to('customer@example.com')
    ->subject('Invoice #1234')
    ->message('Please find your invoice attached.')
    ->attach(storage_path('invoices/invoice-1234.pdf'))
    ->attach(storage_path('invoices/receipt-1234.pdf'))
    ->send();

Multiple Recipients

Call to() multiple times or loop over a list. Invalid email addresses are silently discarded:

php
$mail = (new Mail())
    ->from('newsletter@example.com', 'Example Newsletter')
    ->subject('This week in Slenix')
    ->message($htmlContent, true);

foreach ($subscribers as $subscriber) {
    $mail->to($subscriber->email);
}

$mail->send();

Using Luna Templates

Render a Luna template and pass the result to message():

php
(new Mail())
    ->from('no-reply@example.com', 'Example App')
    ->to($user->email)
    ->subject('Welcome, ' . $user->name . '!')
    ->message(view('emails.welcome', ['user' => $user]), true)
    ->send();

view() returns the compiled HTML string, which is passed directly as the email body.


Practical Examples

Welcome Email (Registration)

php
// In AuthController or UserService
public function register(Request $request, Response $response): void
{
    $user = User::create([
        'name'     => $request->input('name'),
        'email'    => $request->input('email'),
        'password' => $request->input('password'),
    ]);

    (new Mail())
        ->from('no-reply@example.com', 'Example App')
        ->to($user->email)
        ->subject('Welcome to Example App, ' . $user->name . '!')
        ->message(view('emails.welcome', ['user' => $user]), true)
        ->send();

    $response->redirect('/dashboard');
}

Password Reset

php
public function sendResetLink(Request $request): void
{
    $user  = User::firstWhere('email', $request->input('email'));
    $token = bin2hex(random_bytes(32));

    // Store token in DB...

    $link = env('APP_BASE_URL') . '/reset-password?token=' . $token;

    (new Mail())
        ->from('no-reply@example.com', 'Example App')
        ->to($user->email)
        ->subject('Reset your password')
        ->message(view('emails.password-reset', ['link' => $link, 'user' => $user]), true)
        ->send();
}

Invoice With PDF Attachment

php
public function sendInvoice(Order $order): bool
{
    $pdfPath = storage_path("invoices/invoice-{$order->id}.pdf");

    return (new Mail())
        ->from('billing@example.com', 'Example Billing')
        ->to($order->customer->email)
        ->subject("Invoice #{$order->id}")
        ->message(view('emails.invoice', ['order' => $order]), true)
        ->attach($pdfPath)
        ->send();
}

Notification With Manual SMTP Override

Useful when you need to send from a different SMTP account within the same request:

php
(new Mail())
    ->smtp([
        'host'       => 'smtp.sendgrid.net',
        'port'       => 587,
        'username'   => 'apikey',
        'password'   => env('SENDGRID_API_KEY'),
        'encryption' => 'tls',
        'auth'       => true,
    ])
    ->from('alerts@example.com', 'Example Alerts')
    ->to('admin@example.com')
    ->subject('New order received')
    ->message("Order #{$order->id} has been placed by {$order->customer->name}.")
    ->send();

Debugging

The Mail class maintains an internal SMTP conversation log — every command sent and every server response received. Use getDebugLog() to inspect it when a send fails:

php
$mail = (new Mail())
    ->from('test@local.dev')
    ->to('dev@example.com')
    ->subject('SMTP Test')
    ->message('Testing SMTP connection.')
    ->smtp([
        'host'       => 'smtp.mailtrap.io',
        'port'       => 2525,
        'username'   => env('SMTP_USERNAME'),
        'password'   => env('SMTP_PASSWORD'),
        'encryption' => 'tls',
        'auth'       => true,
    ]);

if (!$mail->send()) {
    // Print the full SMTP conversation
    echo nl2br($mail->getDebugLog());
    exit;
}

The log output looks like:

plaintext
>> EHLO my-server.local
<< 250-smtp.mailtrap.io Hello
>> AUTH LOGIN
<< 334 VXNlcm5hbWU6
>> [base64 username]
<< 334 UGFzc3dvcmQ6
>> [base64 password]
<< 235 2.0.0 OK Authenticated
>> MAIL FROM: <test@local.dev>
<< 250 2.1.0 OK
...

Clear the log between sends when reusing an instance:

php
$mail->clearDebugLog();

SMTP Internals

Understanding how Slenix connects helps when diagnosing delivery issues.

TLS (STARTTLS — port 587): Opens a plain connection, sends EHLO, then upgrades to TLS via STARTTLS before authenticating. This is the recommended option for most providers.

SSL (implicit TLS — port 465): Wraps the socket in SSL from the first byte using ssl:// in the hostname. Use this if your provider requires port 465.

none (port 25): Plain connection with no encryption. Only appropriate for internal mail relays on trusted networks. Never use in production over the internet.

The authentication sequence uses AUTH LOGIN, encoding credentials with Base64. This is standard for most SMTP providers including Gmail, SendGrid, and Mailgun.


Security

Always use SMTP in production. PHP's native mail() function bypasses your SMTP credentials, makes it easy for spammers to abuse shared hosting environments, and typically results in lower deliverability.

Store credentials in .env. Never hardcode passwords or API keys directly in code:

php
// ✅ Correct
'password' => env('SMTP_PASSWORD'),

// ❌ Wrong
'password' => 'my-secret-password',

Header injection is mitigated by the fluent API — user-supplied values should be sanitised before being passed to from(), subject(), or message(). Avoid passing raw $_POST values directly:

php
// ✅ Sanitise first
->subject(htmlspecialchars($request->input('subject'), ENT_QUOTES, 'UTF-8'))

// ❌ Raw user input directly into subject
->subject($request->input('subject'))

Email validation is enforced automatically — to() uses filter_var($email, FILTER_VALIDATE_EMAIL) and silently discards invalid addresses.

Attachments are Base64-encoded and sent as Content-Transfer-Encoding: base64, which is safe for binary files. Only files that exist on disk (file_exists()) are attached.


Method Reference

MethodReturnsDescription
from(email, name?)selfSet sender address and optional display name
to(email)selfAdd a recipient (validated, can be called multiple times)
subject(string)selfSet email subject
message(string, isHtml?)selfSet email body; pass true for HTML
attach(filePath)selfAttach a file (must exist on disk)
method(string)selfSet sending method: mail or smtp
smtp(array)selfConfigure SMTP and switch method to SMTP
send()boolSend the email; returns true on success
getDebugLog()stringReturn the full SMTP conversation log
clearDebugLog()voidClear the debug log