How to Send Emails from PHP Using PHPMailer + Gmail SMTP – Complete 2026 Guide

Build This Feature PHPMailer 6.9 Gmail SMTP 2026 App Password HTML Email No Composer Required Verified Working
Build This Feature — PHP Tutorial Series

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.

✅ 2026 verified working 📦 Works without Composer 📨 5 email types with templates 🐛 Error troubleshooter

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.

⛔ Why old tutorials are broken in 2026: Google removed “Less Secure App Access” permanently in May 2022. Any tutorial telling you to “enable less secure app access” in Gmail settings will not work — that option no longer exists. The current requirement is a Gmail App Password, which this tutorial covers in Step 2.
1Install PHPMailer
2Gmail App Password
3Basic Send Function
4HTML Email Template
5With Attachments
6Bulk Notifications

Step 1 — Install PHPMailer

Choose your installation method:

🎼
Method A: Composer (Recommended)
Best practice. Automatically handles dependencies. Use this if you have Composer installed.
📦
Method B: Manual Download (No Composer)
Works on XAMPP without any setup. Download and include the files directly.

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:

  1. Go to your Google Account: myaccount.google.com
  2. Click Security in the left sidebar
  3. Enable 2-Step Verification first if you have not already — App Passwords require 2FA to be active
  4. After enabling 2FA, scroll back to Security → find App passwords (or search for it)
  5. Click App passwords → Select app: Mail → Select device: Other (Custom name)
  6. Type your app name (e.g. “My PHP Project”) → Click Generate
  7. Google shows a 16-character password like abcd efgh ijkl mnopcopy this immediately, it is only shown once
  8. Remove the spaces when using it in your code: abcdefghijklmnop
⚠️ Security note: Never commit your Gmail address or App Password to GitHub. Store them in a separate 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}";
⚠️ Gmail daily sending limit: Gmail SMTP has a limit of approximately 500 emails per day on personal accounts. For bulk sending beyond this, consider using a transactional email service like Mailtrap, Mailgun, or SendGrid — they have PHP libraries that work identically to this pattern but with much higher limits.

Error Troubleshooter — Fix the Most Common PHPMailer Errors

⚠️SMTP ERROR: Password command failed: 534-5.7.9 Application-specific password required
Cause: You are using your regular Gmail password instead of an App Password. Gmail no longer accepts regular passwords for SMTP.
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.
⚠️SMTP connect() failed — Connection timed out or refused on port 587
Most common on shared hosting: Your hosting provider blocks outbound connections on port 587. Try port 465 with SSL instead: $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.
⚠️SMTP Error: Could not authenticate — 535 Username and Password not accepted
Cause 1: The App Password was entered with spaces — remove all spaces from the 16-character password. Google displays it as “abcd efgh ijkl mnop” but it must be entered as “abcdefghijklmnop”.
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.
⚠️Class ‘PHPMailer\PHPMailer\PHPMailer’ not found
Composer install: Make sure 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'
⚠️Email sends on localhost but not on live server
Most common cause: Your hosting provider’s firewall blocks outbound port 587. Try port 465 (SSL) first. If that fails, check whether your host allows outbound SMTP connections at all — many shared hosts restrict this. Contact support and ask “do you allow outbound SMTP connections to smtp.gmail.com on ports 587 and 465?”
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);


How to Generate PDF Reports with FPDF →

Attach generated PDFs to billing emails

PHP Login with Remember Me and Session Timeout →

Send login notification emails from your auth system

Download ISP Management System in PHP →

Add email notifications to your billing system

How to Use AI to Debug Your PHP Code →

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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top