New guide: how to warm up a dedicated IP without hurting your reputation. Read now →
Log in
MigoSMTP
SMTP and APIUpdated 26 Oct 2026

Send Email From PHP With PHPMailer and SMTP: Setup, Errors and Deliverability

Install PHPMailer with Composer, send HTML mail over SMTP with STARTTLS or SSL, attach files, debug connection errors and avoid the problems of the built-in mail() function.

EB
Written by Ethan Brooks
Transactional Email Specialist
Reviewed by Karan Malhotra
  • Published5 May 2026
  • Read time7 min
  • Length1,473 words
Share this guide

PHPMailer is the most widely used library for sending email from PHP. It builds a correct message, handles HTML and attachments, and talks to an SMTP server with encryption and authentication, which the built-in mail() function cannot do on its own. This guide shows a complete working example, the right settings for ports 587 and 465, how to read the debug output, the errors you are most likely to meet and the habits that keep PHP mail out of the spam folder. The examples use placeholders for the host and credentials. Always use the SMTP host, user name and password from your provider's dashboard, and keep them in environment variables or a configuration file outside the web root rather than in your code. If you only need the settings, see SMTP settings: ports, TLS and authentication.

Installcomposer require phpmailer/phpmailer
TransportisSMTP() with host, port, encryption and credentials
Sendone try/catch block per message and a debug level for testing

Why not use mail()?

PHP's mail() function hands the message to a local program, usually sendmail, and returns true when that program accepts it, not when the message is delivered. It has no authentication, no encryption and no error detail. On shared hosting the server's shared IP address sends the message, with no DKIM signature for your domain, which is a poor starting point for the inbox. Many hosts also restrict or disable it. Sending through an SMTP relay with PHPMailer gives you authenticated, encrypted delivery, a proper error message when something fails and a log on the provider side. Our overview of what an SMTP relay is explains the model.

Install PHPMailer

Use Composer. It installs the library and sets up the autoloader. Run this command in your project folder.

composer require phpmailer/phpmailer

If you cannot use Composer, download the library and include the three source files by hand, but Composer makes updates, which include security fixes, much easier. PHPMailer needs PHP 5.5 or later at a minimum, and a currently supported PHP version is strongly recommended.

A complete example with STARTTLS on port 587

This script sends an HTML message with a plain-text alternative. Credentials come from environment variables. Port 587 with STARTTLS is the standard choice for applications.

<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;

require __DIR__ . '/vendor/autoload.php';

$mail = new PHPMailer(true); // true = throw exceptions

try {
    $mail->isSMTP();
    $mail->Host       = getenv('SMTP_HOST');      // e.g. smtp.example.com
    $mail->SMTPAuth   = true;
    $mail->Username   = getenv('SMTP_USER');
    $mail->Password   = getenv('SMTP_PASS');
    $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
    $mail->Port       = 587;
    $mail->CharSet    = 'UTF-8';

    $mail->setFrom('[email protected]', 'Example App');
    $mail->addReplyTo('[email protected]', 'Example Support');
    $mail->addAddress('[email protected]', 'A Customer');

    $mail->isHTML(true);
    $mail->Subject = 'Welcome to Example';
    $mail->Body    = '<p>Thanks for signing up.</p>';
    $mail->AltBody = 'Thanks for signing up.';

    $mail->send();
    echo 'Sent';
} catch (Exception $e) {
    error_log('Mailer error: ' . $mail->ErrorInfo);
    echo 'The message could not be sent.';
}

The constant ENCRYPTION_STARTTLS corresponds to the string tls and means "connect in plain text, then upgrade". Use PHPMailer::ENCRYPTION_SMTPS (the string ssl) together with port 465 if you want encryption from the first byte. Never mix them: STARTTLS on port 465, or SMTPS on port 587, fails with a handshake error.

Which settings go together

PortSMTPSecure constantBehaviour
587PHPMailer::ENCRYPTION_STARTTLSStarts unencrypted and upgrades with STARTTLS. Recommended for most applications
465PHPMailer::ENCRYPTION_SMTPSEncrypted from the first byte (implicit TLS)
25noneServer-to-server port, often blocked for applications. Avoid it
2525PHPMailer::ENCRYPTION_STARTTLSOffered by some providers as an alternative if 587 is blocked. Check your provider

If you are unsure which port to choose, read which SMTP port to use. Do not set SMTPAutoTLS to false or turn off certificate checks to "make it work". Disabling verification hides a real problem and exposes your credentials to interception.

Attachments, Cc and Bcc, and multiple recipients

Add files with addAttachment, passing a path and an optional display name. Add copies with addCC and addBCC. Each recipient address is one call. For embedded images, use addEmbeddedImage with a content ID and reference it in the HTML as cid:logo. Keep attachments small: the combined size of the message, after encoding, has to fit within the receiving server's limit, and encoding makes files about one third larger. See email attachment size limits for common limits.

$mail->addAttachment('/path/to/invoice.pdf', 'Invoice-1042.pdf');
$mail->addCC('[email protected]');
$mail->addBCC('[email protected]');

Debug output: see the whole SMTP conversation

When a send fails, turn on debug output. It prints every command and reply, which usually shows the cause in seconds. Use it in development only, and never leave it on in production, because it can print credentials in logs.

use PHPMailer\PHPMailer\SMTP;

$mail->SMTPDebug   = SMTP::DEBUG_SERVER;   // client and server messages
$mail->Debugoutput = function ($str, $level) {
    error_log("SMTP debug [$level]: $str");
};

Read the output in order: the connection, the EHLO reply that lists the server's features, the STARTTLS exchange, the AUTH step and finally the MAIL FROM, RCPT TO and DATA commands. The first line that shows an error code is the one to search for.

Common errors and what they mean

MessageUsual causeFix
SMTP connect() failedThe server cannot reach the host or port, or a firewall blocks itCheck the host name, the port and outbound firewall rules, and test with openssl s_client
Could not authenticate / 535Wrong credentials, or credentials in the wrong formatSee our guide to SMTP error 535
Failed to connect to server (connection timed out)Outbound connections on that port are blocked, common on some shared hostsTry a different supported port or ask the host to allow outbound SMTP
SSL: wrong version numberSTARTTLS used on port 465 or SMTPS used on port 587Match the encryption constant to the port
certificate verify failedOutdated CA bundle on the server, or a proxy that intercepts TLSUpdate the CA certificates. Do not disable verification
Sender address rejectedThe From address is not allowed for this account or domain is not verifiedSend from an address on a verified domain
Message body emptyNeither Body nor AltBody was setSet a Body and an AltBody
Invalid addressA malformed recipient addressValidate input with filter_var before calling addAddress

Structuring mail code in a real application

A script like the one above is fine for a first test, but in an application you should wrap the library once and reuse it. Create a small class or service that builds a configured PHPMailer instance from your settings, so that host, port and credentials live in one place. Pass in the recipient, subject and content. Return a result that your code can check. Keep message templates separate from sending logic so a designer can change a template without touching delivery code. Reuse one connection for several messages with SMTPKeepAlive = true and call clearAddresses() and clearAttachments() between messages, then call smtpClose() at the end. Without clearing, the second message would also be sent to the first message's recipient.

Do not send mail inside the web request when a user is waiting, if you can avoid it. SMTP is slower than a database query, and a provider timeout would freeze the page. Put messages on a queue and let a worker send them, with retries that follow the rules in rate limits, retries and backoff. For high volume, an email API can be a better fit than SMTP, which our comparison of SMTP and API sending explains.

Security notes

  • Keep credentials in environment variables or a protected config file, never in version control.
  • Never build the From address or recipient from raw user input. An attacker who can inject a line break into a header can add recipients. PHPMailer rejects newlines in addresses, but validate input anyway.
  • Rate limit contact and sign-up forms and add a CAPTCHA or token check so that your form cannot be used to send spam.
  • Keep PHPMailer updated through Composer. Older versions have had security fixes.
  • Set a sensible timeout so that a slow server cannot hang your process: $mail->Timeout = 15;.

Deliverability for PHP mail

A correct library does not guarantee the inbox. Three things matter most. First, authenticate your domain with SPF, DKIM and DMARC, and send from an address on that domain. Second, send a clear plain-text alternative along with the HTML, and keep the content honest, because filters penalise empty or image-only messages. Third, separate transactional mail from marketing mail, so that a marketing complaint does not harm password reset delivery. The guides on authenticating your sending domain and separate transactional and marketing streams cover these steps.

Send from PHP through a managed relay Get the SMTP host and credentials from your dashboard and connect PHPMailer in minutes.

Explore the SMTP relay

References and further reading

Frequently asked questions

Which port should I use with PHPMailer?

Use port 587 with STARTTLS for most applications. Use port 465 with SMTPS if your provider recommends implicit TLS. Match the encryption setting to the port.

Why does PHP mail() not work?

mail() relies on a local mail program, often disabled or restricted on hosts, and it has no authentication or error detail. Sending through SMTP with PHPMailer is more reliable.

How do I see the SMTP conversation?

Set SMTPDebug to SMTP::DEBUG_SERVER in development and send a test. The output shows each command and server reply, which usually reveals the cause.

Is it safe to turn off certificate verification?

No. It hides real TLS problems and exposes credentials to interception. Fix the certificate or CA bundle instead.

Should I send email inside the page request?

Avoid it when possible. Use a queue and a worker so that a slow SMTP server does not delay the page, and so that failed messages can be retried.

Topics#php#phpmailer#smtp#code examples#developers
Was this guide helpful?Your answer helps us improve our documentation.

Start sending in minutes.

Create an account, add your domain and send your first email.

Get started Talk to sales