How to Send an Email via Gmail SMTP Server Using PHP

How I broke it, what that cryptic error actually means, and the cleaner code I use today plus a few drills you can practice on your own.

Error Code

I just wanted to ship a friendly “Hello” from a PHP page. Instead, Gmail greeted me with this brick wall:

Authentication failure [SMTP: SMTP server does no support authentication
(code: 250, response: mx.google.com at your service, [98.117.99.235]
SIZE 35651584 8BITMIME STARTTLS ENHANCEDSTATUSCODES PIPELINING)]

At first glance it screams, “Gmail won’t let you log in,” but hidden in the noise is the clue: STARTTLS.

My First Snippet

<?php
require_once "Mail.php"; // PEAR Mail

$from = "Sandra Sender <sender@example.com>";
$to = "Ramona Recipient <ramona@microsoft.com>";
$subject = "Hi!";
$body = "Hi,\n\nHow are you?";

$host = "smtp.gmail.com";
$port = 587; // Gmail’s TLS port
$username = "testtest@gmail.com";
$password = "testtest"; // never hard-code real secrets

$headers = [
'From' => $from,
'To' => $to,
'Subject' => $subject
];

$smtp = Mail::factory('smtp', [
'host' => $host,
'port' => $port,
'auth' => true,
'username' => $username,
'password' => $password
]);

$result = $smtp->send($to, $headers, $body);

echo PEAR::isError($result)
? "<p>" . $result->getMessage() . "</p>"
: "<p>Message successfully sent!</p>";
?>

Why it Fizzled

SymptomPlain-English cause
SMTP server does no support authentication (code: 250)Gmail is happy to talk, but on port 587 it demands a STARTTLS handshake before it will listen to any username or password. PEAR Mail tried to log in first—Gmail politely refused.

A couple more rules bit me too:

  • Since May 2022 Gmail blocks “Less Secure Apps.” You need an app-specific password or OAuth2 your everyday login password no longer works.
  • PEAR Mail doesn’t flip the TLS switch automatically.

Quick Patch While Staying on PEAR Mail

$smtp = Mail::factory('smtp', [
'host' => 'tls://smtp.gmail.com', // tell PEAR to negotiate TLS
'port' => 587,
'auth' => true,
'username' => getenv('GMAIL_USER'), // read from env vars
'password' => getenv('GMAIL_APP_PASS')
]);

This does work, but PEAR Mail is frozen in the early 2010s and has no native OAuth2 helper. That sent me hunting for a friendlier library.

The Clean UpGrade: PHPMailer

Install in one line:

composer require phpmailer/phpmailer

Drop the sample below into send-gmail.php and run it:

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

require 'vendor/autoload.php';

$mail = new PHPMailer(true); // throws Exceptions

try {
/* --- server --- */
$mail->isSMTP();
$mail->Host = 'smtp.gmail.com';
$mail->SMTPAuth = true;
$mail->Username = getenv('GMAIL_USER'); // me@gmail.com
$mail->Password = getenv('GMAIL_APP_PASS'); // 16-char app password
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
$mail->Port = 587;

/* --- recipients --- */
$mail->setFrom('sender@example.com', 'Sandra Sender');
$mail->addAddress('ramona@microsoft.com', 'Ramona Recipient');
$mail->addReplyTo('noreply@example.com', 'No-reply');

/* --- content --- */
$mail->isHTML(true);
$mail->Subject = 'Hi!';
$mail->Body = '<p>Hi Ramona,<br><br>How are you?</p>';
$mail->AltBody = "Hi Ramona,\n\nHow are you?";

$mail->send();
echo 'Message sent!';
} catch (Exception $e) {
echo "Mailer Error: {$mail->ErrorInfo}";
}
?>

Why this works

  • SMTPSecure = STARTTLS forces encryption before AUTH.
  • App passwords dodge Gmail’s “Less Secure Apps” block without a full OAuth2 dance.
  • PHPMailer prints crystal-clear errors when something goes sideways.

Stretch Goals

DrillOne-liner to drop in
Attach a file$mail->addAttachment(__DIR__.'/invoice.pdf');
Send CC/BCC$mail->addCC('boss@example.com');
$mail->addBCC('audit@example.com');
Watch every SMTP command$mail->SMTPDebug = 2; (great on a staging box—turn off in production)
Load secrets from .envcomposer require vlucas/phpdotenv then $_ENV['GMAIL_USER']
HTML contact formPair a small index.html with action="send-gmail.php"; sanitize POST fields before calling $mail->Body.
Full OAuth2 (hard-mode)Register a Google Cloud project, enable Gmail API, and use PHPMailer’s OAuth class.

Work through them one at a time enable SMTPDebug, re-run, and watch the live conversation: EHLO, STARTTLS, AUTH, MAIL FROM, RCPT TO, DATA, QUIT. Seeing the handshake click into place is oddly satisfying.

Final Thought

The scary-looking error wasn’t cryptic at all once I read between the brackets it was Gmail saying, “Talk TLS to me first.” As soon as I negotiated encryption and switched to an app password, Gmail behaved like any normal SMTP server.

Related blog posts