How to Send Emails from PHP Using PHPMailer + Gmail SMTP — Complete 2026 Guide
PHP’s built-in mail() function stopped working reliably years ago, and Google removed “Less Secure App” access in May 2022. Every tutorial written before 2022 is broken. This guide covers the only two approaches that actually work in 2026: PHPMailer with a Gmail App Password, and PHPMailer without Composer — so even XAMPP students without Composer can implement it.
Sending emails from a PHP project used to be simple. You called mail(), passed four parameters, and it worked. Then Google tightened security, hosting providers blocked port 25, and everything broke. The fix is PHPMailer with Gmail SMTP and an App Password — which is the current working standard in 2026.
This tutorial covers everything: installing PHPMailer (with and without Composer), creating a Gmail App Password, sending plain text and HTML emails, adding attachments, sending bulk notifications, and diagnosing every common error. By the end you will have a working sendEmail() function you can drop into any PHP project.
Step 1 — Install PHPMailer
Choose your installation method:
Terminal Method A — Install via Composer
# Navigate to your project folder in terminal/cmd first
composer require phpmailer/phpmailer
# This creates a vendor/ folder with PHPMailer and its dependencies
# Your project structure will look like:
# your-project/
# ├── vendor/
# │ └── phpmailer/phpmailer/src/
# ├── composer.json
# └── your PHP files
Step 2 — Create a Gmail App Password (Required in 2026)
A Gmail App Password is a 16-character password that Google generates specifically for an application. It replaces your real Gmail password for SMTP authentication.
Step-by-step to generate your App Password:
- Go to your Google Account: myaccount.google.com
- Click Security in the left sidebar
- Enable 2-Step Verification first if you have not already — App Passwords require 2FA to be active
- After enabling 2FA, scroll back to Security → find App passwords (or search for it)
- Click App passwords → Select app: Mail → Select device: Other (Custom name)
- Type your app name (e.g. “My PHP Project”) → Click Generate
- Google shows a 16-character password like
abcd efgh ijkl mnop— copy this immediately, it is only shown once - Remove the spaces when using it in your code:
abcdefghijklmnop
config.php file that is listed in your .gitignore. The examples below use constants — define these in your config file, not directly in the mail-sending code.
PHP config/email_config.php — store credentials here, add to .gitignore
<?php
// config/email_config.php — add this file to .gitignore!
define('SMTP_FROM_EMAIL', 'yourgmail@gmail.com');
define('SMTP_FROM_NAME', 'Your Project Name');
define('SMTP_APP_PASSWORD', 'abcdefghijklmnop'); // 16-char App Password, no spaces
define('SMTP_HOST', 'smtp.gmail.com');
define('SMTP_PORT', 587);
?>
Step 3 — The Core sendEmail() Function
This reusable function is the foundation for all email sending in your project. Call it from anywhere — new user registration, billing notifications, contact form submissions, password resets.
PHP helpers/mailer.php — reusable sendEmail() function
<?php
// helpers/mailer.php — include this wherever you need to send email
require_once '../config/email_config.php';
// Composer install:
require_once '../vendor/autoload.php';
// Manual install (use instead of autoload):
// require_once '../phpmailer/src/Exception.php';
// require_once '../phpmailer/src/PHPMailer.php';
// require_once '../phpmailer/src/SMTP.php';
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
/**
* Send an email using PHPMailer + Gmail SMTP
*
* @param string $to Recipient email address
* @param string $toName Recipient display name
* @param string $subject Email subject line
* @param string $body HTML body (can include HTML tags)
* @param string $altBody Plain text fallback (for email clients that block HTML)
* @param array $attachments Optional array of file paths to attach
* @return bool|string true on success, error message string on failure
*/
function sendEmail($to, $toName, $subject, $body,
$altBody = '', $attachments = []) {
$mail = new PHPMailer(true); // true = throw exceptions on error
try {
// ── Server configuration ──────────────────────────────
$mail->isSMTP();
$mail->Host = SMTP_HOST;
$mail->SMTPAuth = true;
$mail->Username = SMTP_FROM_EMAIL;
$mail->Password = SMTP_APP_PASSWORD;
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; // TLS on port 587
$mail->Port = SMTP_PORT; // 587
// ── Sender and recipient ───────────────────────────────
$mail->setFrom(SMTP_FROM_EMAIL, SMTP_FROM_NAME);
$mail->addAddress($to, $toName);
$mail->addReplyTo(SMTP_FROM_EMAIL, SMTP_FROM_NAME);
// ── Content ────────────────────────────────────────────
$mail->isHTML(true);
$mail->Subject = $subject;
$mail->Body = $body; // HTML version
$mail->AltBody = $altBody ?: strip_tags($body); // plain text fallback
$mail->CharSet = PHPMailer::CHARSET_UTF8;
// ── Optional attachments ──────────────────────────────
foreach ($attachments as $file) {
if (file_exists($file)) {
$mail->addAttachment($file);
}
}
$mail->send();
return true;
} catch (Exception $e) {
// Log error to server, return message to caller
error_log('PHPMailer Error: ' . $mail->ErrorInfo);
return $mail->ErrorInfo;
}
}
?>
Step 4 — Email Templates for 5 Common Use Cases
Send a Welcome Email After User Registration
PHP Call after inserting a new user into the database
require_once '../helpers/mailer.php';
function sendWelcomeEmail($email, $name, $username) {
$subject = 'Welcome to Our System, ' . $name . '!';
$body = '<!DOCTYPE html><html><body style="font-family:Arial,sans-serif;color:#333">
<div style="max-width:600px;margin:0 auto;padding:30px">
<h2 style="color:#0057b8">Welcome aboard, ' . htmlspecialchars($name) . '!</h2>
<p>Your account has been created successfully.</p>
<table style="border:1px solid #ddd;padding:15px;border-radius:8px;background:#f8f9fa">
<tr><td><strong>Username:</strong></td><td> . htmlspecialchars($username) . '</td></tr>
<tr><td><strong>Email:</strong></td><td> . htmlspecialchars($email) . '</td></tr>
</table>
<p style="margin-top:20px">
<a href="https://yoursite.com/login" style="background:#0057b8;color:#fff;
padding:10px 24px;border-radius:6px;text-decoration:none;font-weight:700">
Log In Now
</a>
</p>
</div>
</body></html>';
return sendEmail($email, $name, $subject, $body);
}
// Call it after INSERT into users table:
// $result = sendWelcomeEmail($email, $fullName, $username);
// if ($result !== true) { error_log("Email failed: $result"); }
Monthly Billing Notification Email
PHP Send bill notification to customer
function sendBillingEmail($email, $name, $amount, $dueDate, $billRef) {
$subject = 'Your Bill for ' . date('F Y') . ' — Ref: ' . $billRef;
$body = '<!DOCTYPE html><html><body style="font-family:Arial,sans-serif">
<div style="max-width:600px;margin:0 auto;padding:30px">
<h2 style="color:#0057b8">Monthly Bill — ' . date('F Y') . '</h2>
<p>Dear . htmlspecialchars($name) . ,</p>
<p>Your bill for this month is ready.</p>
<div style="background:#f0f6ff;border-left:4px solid #0057b8;padding:16px;margin:16px 0">
<p><strong>Reference:</strong> . htmlspecialchars($billRef) . '</p>
<p><strong>Amount Due:</strong> <span style="font-size:1.4em;color:#0057b8">
£ . number_format($amount, 2) . '</span></p>
<p><strong>Due Date:</strong> . $dueDate . '</p>
</div>
<p>Please log in to your account to view your bill and make payment.</p>
</div></body></html>';
return sendEmail($email, $name, $subject, $body);
}
Password Reset Email
PHP Send a password reset link with a secure token
function sendPasswordReset($email, $name, $resetToken) {
$resetUrl = 'https://yoursite.com/reset-password.php?token='
. urlencode($resetToken);
$subject = 'Password Reset Request';
$body = '<!DOCTYPE html><html><body style="font-family:Arial,sans-serif">
<div style="max-width:600px;margin:0 auto;padding:30px">
<h2>Password Reset Request</h2>
<p>Hi . htmlspecialchars($name) . ,</p>
<p>We received a request to reset your password.
Click the button below to set a new password.
This link expires in 1 hour.</p>
<p>
<a href=" . $resetUrl . '" style="background:#dc2626;color:#fff;
padding:12px 28px;border-radius:6px;text-decoration:none;font-weight:700">
Reset My Password
</a>
</p>
<p style="color:#888;font-size:0.85em">
If you did not request this, ignore this email.
Your password will not change.
</p>
</div></body></html>';
return sendEmail($email, $name, $subject, $body);
}
Contact Form Submission
PHP Forward a contact form submission to your inbox
function sendContactFormEmail($fromEmail, $fromName, $message) {
$subject = 'New Contact Form Message from ' . $fromName;
$body = '<p><strong>From:</strong> '
. htmlspecialchars($fromName) . ' <'
. htmlspecialchars($fromEmail) . '></p>
<p><strong>Message:</strong></p>
<p style="background:#f5f5f5;padding:15px;border-radius:6px">'
. nl2br(htmlspecialchars($message))
. '</p>';
// Send to YOUR inbox (SMTP_FROM_EMAIL = your Gmail)
return sendEmail(SMTP_FROM_EMAIL, 'Admin', $subject, $body);
}
Bulk Notifications — Send to Multiple Users
PHP Send billing notifications to all active customers
require_once '../config/dbconnection.php';
require_once '../helpers/mailer.php';
$result = mysqli_query($conn,
"SELECT c.name, c.email, b.amount, b.ref, b.due_date
FROM bills b
JOIN customers c ON b.customer_id = c.id
WHERE b.status = 'unpaid' AND b.notification_sent = 0");
$sent = 0; $failed = 0;
while ($row = mysqli_fetch_assoc($result)) {
$emailResult = sendBillingEmail(
$row['email'], $row['name'],
$row['amount'], $row['due_date'], $row['ref']
);
if ($emailResult === true) {
$sent++;
// Mark as sent so we don't send twice
mysqli_query($conn,
"UPDATE bills SET notification_sent=1 WHERE ref='"
. mysqli_real_escape_string($conn, $row['ref']) . "'");
} else {
$failed++;
error_log("Failed to send to " . $row['email'] . ": " . $emailResult);
}
usleep(200000); // 0.2 second pause between emails — respects Gmail rate limits
}
echo "Sent: {$sent}, Failed: {$failed}";
Error Troubleshooter — Fix the Most Common PHPMailer Errors
Fix: Follow Step 2 above to generate a 16-character App Password. Use that in your config instead of your Gmail password. Note: App Passwords require 2-Step Verification to be enabled on your Google account first.
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; $mail->Port = 465;If still failing, your host may block all outbound SMTP entirely — contact their support or use a transactional email relay service like SendGrid or Mailgun which provide HTTP APIs that bypass SMTP restrictions.
On XAMPP (localhost): Gmail will not accept connections from a local IP (192.168.x.x) — you must deploy to a live server or use a local SMTP testing tool like MailHog for development testing.
Cause 2: 2FA is not enabled on the Gmail account — App Passwords do not exist without 2-Step Verification active.
Cause 3: The App Password was regenerated but the old one is still in your config file — copy the new one carefully.
require_once 'vendor/autoload.php' is included and the vendor folder exists in your project root. Run composer install again if the vendor folder is missing.Manual install: Make sure all three files are included — Exception.php, PHPMailer.php, and SMTP.php — and that the paths are correct relative to the file doing the including. Use
__DIR__ for reliable absolute paths: require_once __DIR__ . '/../phpmailer/src/PHPMailer.php'Second cause: PHP version differences — make sure your live server runs PHP 7.3 or above (PHPMailer 6.x requirement) and has the OpenSSL extension enabled.
Pre-Send Checklist
✅ Before Your First Production Email — Verify These
Frequently Asked Questions
Can I test email sending on XAMPP localhost without using Gmail?
Yes — use MailHog. It is a local SMTP server that catches emails and shows them in a web interface instead of sending them. It is free, runs locally, and requires no Gmail credentials. Download from github.com/mailhog/MailHog, run the executable, then configure PHPMailer to use host “127.0.0.1” port 1025 with no auth. This is the professional way to test email functionality during development — you never risk accidentally sending test emails to real addresses.
My emails go to the spam folder. How do I fix this?
Gmail-to-Gmail emails almost never go to spam because Google trusts its own SMTP servers. Gmail-to-other-providers can land in spam because the sending IP (Google’s SMTP server) may not be in the SPF/DKIM records for your custom domain. For production applications that send emails from a custom domain (yourname@yoursite.com rather than yourname@gmail.com), use a transactional email service like Mailgun or SendGrid — they handle SPF, DKIM, and DMARC configuration for you, which dramatically improves deliverability. For student projects and portfolios using Gmail, this is not an issue.
How do I add a PDF attachment to the billing email?
Generate the PDF first (see the FPDF tutorial on Codezips), save it to a temporary file, then pass the file path in the $attachments array of the sendEmail() function: sendEmail($email, $name, $subject, $body, '', ['/tmp/invoice_123.pdf']). Delete the temporary file after sending: if (file_exists($tmpPath)) unlink($tmpPath);
Attach generated PDFs to billing emails
Send login notification emails from your auth system
Add email notifications to your billing system
Debug PHPMailer errors with the right AI prompts
Last updated April 2026. PHPMailer 6.9 used. Gmail App Password requirement confirmed from Google support documentation. SMTP port and encryption settings verified working April 2026.


