data types and standard library

Email and MIME Handling

PHP applications send email for account verification, password resets, invoices, receipts, notifications, alerts, and support workflows. The hard part is not only "send a string"; it is building a safe message and handing delivery to a reliable mailer or provider.

For production, prefer a maintained mailer library or transactional email provider. They handle MIME boundaries, encodings, SMTP/API details, attachments, retries, and delivery diagnostics better than hand-built calls to mail().

Validate recipient addresses

Validate addresses before building the message.

PHP example
<?php

declare(strict_types=1);

function requireEmailAddress(string $email): string
{
    $email = trim($email);

    if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
        throw new InvalidArgumentException('Invalid email address.');
    }

    return strtolower($email);
}

echo requireEmailAddress(' NIA@example.com ') . PHP_EOL;

// Prints:
// nia@example.com

This validates syntax, not deliverability. A syntactically valid address can still bounce.

Prevent header injection

Email headers must not contain raw newlines from user input. A malicious value could try to add extra headers.

PHP example
<?php

declare(strict_types=1);

function assertHeaderValue(string $value): string
{
    if (str_contains($value, "\r") || str_contains($value, "\n")) {
        throw new InvalidArgumentException('Header value contains a newline.');
    }

    return $value;
}

try {
    assertHeaderValue("Welcome\nBcc: attacker@example.com");
} catch (InvalidArgumentException $exception) {
    echo $exception->getMessage() . PHP_EOL;
}

// Prints:
// Header value contains a newline.

Mailer libraries usually protect this boundary, but you still need to understand why raw header concatenation is risky.

Encode non-ASCII header text

Headers have different encoding rules from message bodies. Use MIME-aware encoding for header text.

PHP example
<?php

declare(strict_types=1);

$subject = 'Résumé received';
$encodedSubject = mb_encode_mimeheader($subject, 'UTF-8');

echo str_contains($encodedSubject, '=?UTF-8?') ? 'encoded subject' : $encodedSubject;
echo PHP_EOL;

// Prints:
// encoded subject

Do not assume a subject or sender display name contains only ASCII.

Keep plain text and HTML bodies separate

Many emails include both a plain text body and an HTML body.

PHP example
<?php

declare(strict_types=1);

$name = 'Nia & Co';

$plainText = 'Hello ' . $name . ', your order is ready.';
$html = '<p>Hello ' . htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . ', your order is ready.</p>';

echo $plainText . PHP_EOL;
echo $html . PHP_EOL;

// Prints:
// Hello Nia & Co, your order is ready.
// <p>Hello Nia &amp; Co, your order is ready.</p>

HTML email still needs HTML escaping. Plain text and HTML are different output contexts.

Attachments need MIME metadata

An attachment is more than a file path. You need the display filename, MIME type, and content source.

PHP example
<?php

declare(strict_types=1);

function attachmentMetadata(string $filename, string $mimeType): array
{
    if (basename($filename) !== $filename || $filename === '') {
        throw new InvalidArgumentException('Attachment filename is invalid.');
    }

    if (!str_contains($mimeType, '/')) {
        throw new InvalidArgumentException('Attachment MIME type is invalid.');
    }

    return [
        'filename' => $filename,
        'mimeType' => $mimeType,
    ];
}

$attachment = attachmentMetadata('invoice-2026-001.pdf', 'application/pdf');

echo $attachment['filename'] . ' / ' . $attachment['mimeType'] . PHP_EOL;

// Prints:
// invoice-2026-001.pdf / application/pdf

Validate attachments before passing them to a mailer, especially when files originate from uploads or generated storage.

Queue important mail

Sending email during a web request can make the user wait and makes failures awkward. Many applications record an email job, then a worker sends it.

PHP example
<?php

declare(strict_types=1);

$job = [
    'type' => 'send_email',
    'template' => 'order.receipt',
    'recipient' => 'nia@example.com',
];

echo $job['type'] . ' / ' . $job['template'] . PHP_EOL;

// Prints:
// send_email / order.receipt

Queues also make retries, failure logs, and provider outages easier to handle.

What to remember

Validate addresses, protect headers from newlines, encode header text, escape HTML bodies, describe attachments with MIME metadata, and use a proper mailer or provider for production delivery. Treat email as an external integration with logs and failure handling, not as a simple string write.

Practice

Task: Prepare a safe email message

Write a small helper that prepares email message data without sending it.

Requirements

  • Use declare(strict_types=1);.
  • Validate and normalise the recipient email address.
  • Reject subject text containing \r or \n.
  • Encode the subject with mb_encode_mimeheader().
  • Build a plain text body.
  • Build an HTML body and escape user-controlled text.
  • Return a message array containing recipient, encoded subject, plain text body, and HTML body.
  • Show one valid message and one rejected subject.
  • Include the expected output as comments in the same PHP code block.

Do not call mail(). The goal is safe message preparation before a real mailer sends it.

Show solution
PHP example
<?php

declare(strict_types=1);

function prepareWelcomeEmail(string $recipient, string $subject, string $name): array
{
    $recipient = trim($recipient);

    if (filter_var($recipient, FILTER_VALIDATE_EMAIL) === false) {
        throw new InvalidArgumentException('Invalid recipient address.');
    }

    if (str_contains($subject, "\r") || str_contains($subject, "\n")) {
        throw new InvalidArgumentException('Subject contains a newline.');
    }

    $name = trim($name);

    if ($name === '') {
        throw new InvalidArgumentException('Recipient name is required.');
    }

    return [
        'recipient' => strtolower($recipient),
        'subject' => mb_encode_mimeheader($subject, 'UTF-8'),
        'text' => 'Hello ' . $name . ', welcome to the course.',
        'html' => '<p>Hello ' . htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . ', welcome to the course.</p>',
    ];
}

$message = prepareWelcomeEmail(' NIA@example.com ', 'Welcome', 'Nia & Co');

echo $message['recipient'] . PHP_EOL;
echo $message['subject'] . PHP_EOL;
echo $message['html'] . PHP_EOL;

try {
    prepareWelcomeEmail('nia@example.com', "Welcome\nBcc: attacker@example.com", 'Nia');
} catch (InvalidArgumentException $exception) {
    echo $exception->getMessage() . PHP_EOL;
}

// Prints:
// nia@example.com
// Welcome
// <p>Hello Nia &amp; Co, welcome to the course.</p>
// Subject contains a newline.

The helper prepares the message data safely but leaves actual delivery to a mailer. It validates the recipient, protects the header boundary, and escapes the HTML body separately from the plain text body.