Upgrading PHPMail to Gmail SMTP using PHPMailer and Google reCAPTCHA

Contact forms are a must on websites, preventing the need for email addresses to be on display and at risk to search-engine spiders as well as providing anti-spam protection from bots.

In the past, I’ve used a simple PHP mail() setup which relies on the hosting plan’s local mail server, configured in the php.ini file. However, within shared hosting plans which I, like many people, rely on, changing or accessing this file is not possible. There can also be issues regarding the perceived safety of receiving emails from this source by mail providers, causing contact form emails to be incorrectly marked as spam.

Setting up a trustworthy, and still free, Google SMTP contact form is not as big of a leap as I first thought it was and only requires a few small amendments to the contact form process and a few dependency files from PHPMailer.


Editing Gmail settings ready for SMTP

You will need a Gmail account and the password for it to use for this. The username and password are added into the code, allowing PHPMailer to authenticate and use the account to send the emails.

The first thing to do is allow “less secure apps” to authenticate:

❗ Google removed the “less secure apps” option outside of Google Workspaces (formally G Suite) in May 2022. Instead you should use either app passwords and two factor authentication or the API.

  1. Log in to Gmail with the account
  2. Go to My Account and then Security
  3. Scroll down to “less secure apps” and, if it’s OFF, click the button
  4. Toggle “less secure apps” to ON

And the second is to enable IMAP:

  1. Now go to your Gmail inbox
  2. Click the Settings gear and See all settings
  3. Click the Forwarding and POP/IMAP settings tab
  4. Enable IMAP and Save Changes

With IMAP on you will be able to see the emails that are sent in the Gmail Sent folder. This can be quite useful to check it’s working, but be careful if sensitive information is being shared with the form as anyone with access to this Gmail would be able to see form submissions.


Setting up Google reCAPTCHA

Google reCAPTCHA verifies the user is legitimate and not a bot, helping prevent fraudulent or spam emails being sent. There are several types, an invisible automatic check, a checkbox or a puzzle to solve. Personally I still use a v2 “I’m not a robot” checkbox, as it gives my clients confidence that a user has to tick it.

  • Visit the Google reCAPTCHA admin panel
  • If you do not have one set up already it will default to register a new site or click the plus button to add a new one if you do
  • Label, for your own record, the form (For example “Contact form” or use your domain name to tell them apart)
  • Choose the type of reCAPTCHA you wish to use
  • Add the URL for your domain
  • Agree to the Terms of Service then Submit
  • Save the API keys to a secure location, these will be needed later

Installing PHPMailer

For me, simply uploading via FTP (or via a host’s File Manager) is enough to install the three required files into the correct directory. More complicated installation instructions can be found on PHPMailer’s GitHub documentation, if Composer or Packagist are your preferred choice.

  1. Download the zip folder of files via GitHub
  2. Extract the files using your preferred method
  3. Within your root directory, create a directory called vendor
  4. Within vendor, create another directory called phpmailer
  5. Within phpmailer, create another directory called src
  6. Upload the following three files to the src directory:
    • Exception.php
    • PHPMailer.php
    • SMTP.php

There, that’s it. I’m sure there’s an advantage to the more complicated methods of installation, but this works and can be done with entry-level knowledge.


Form front-end

None of the front-end of a contact form needs to change at all between a PHP mail form to become a PHPMailer form.

Simply containing three inputs (name, email and message), this form is only using some default Bootstrap 5 styling. Important to include on the front-end of the form, just before the submit button, is the Google reCAPTCHA public key:

<div class="g-recaptcha mb-3" data-sitekey="PUBLICKEY"></div>


Full front-end form code

Optional success/failure messaging using GET to get the parameter status from the URL:

<?php

if(isset($_GET['status'])) {
	$status = $_GET['status'];
	if($status == 0) {
		echo '<p><strong>Form submitted:</strong>&nbsp;Thank you for submitting our contact form, we\'ll get back to you soon!</p>';
	} else if($status == 1) {
		echo '<p><strong>Error:</strong>&nbsp;Unfortunately, something has gone wrong with the form. Please try again.</p>';
	}
}

?>

And the form itself:

<form action="/contact.php" method="post">
	<div class="mb-3">
		<label for="name" class="form-label"><strong>Name</strong></label>
		<input class="form-control" type="text" id="name" name="contact_name" pattern=[A-Z\sa-z]{3,20} required>
	</div>
	<div class="mb-3">
		<label for="email" class="form-label"><strong>Email</strong></label>
		<input class="form-control" type="email" id="email" name="contact_email" required>
	</div>
	<div class="mb-3">
		<label for="message" class="form-label"><strong>Message</strong></label>
		<textarea class="form-control" style="resize: none;" id="message" name="contact_message" placeholder="Please include any relevant information" rows="5" required></textarea>
	</div>

	<div class="g-recaptcha mb-3" data-sitekey="PUBLICKEY"></div>

	<button type="submit" value="submit" name="submit" class="btn btn-lg btn-dark mb-3">Submit</button>
</form>

Just remember to change the action if the form process is elsewhere and enter your own public key for reCAPTCHA.


Form back-end

The purpose of the form process.php is to:

  • Include the required PHPMailer files uploaded earlier
  • Verify the Google reCAPTCHA
  • Create a HTML email with the submitted form contents
  • Set up an SMTP connection
  • Send the HTML email to the set recipient

Form back-end step-by-step

The first step is to set the secret key for Google reCAPTCHA and include the three required PHPMailer files:

<?php

$secretKey = 'SECRETKEY'; 

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

require_once $_SERVER['DOCUMENT_ROOT'].'/vendor/phpmailer/src/Exception.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/vendor/phpmailer/src/PHPMailer.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/vendor/phpmailer/src/SMTP.php';

Next step, if the form was submitted using the button, is to verify the Google reCAPTCHA:

if(isset($_POST['submit'])) {

	if(isset($_POST['g-recaptcha-response']) && !empty($_POST['g-recaptcha-response'])) {

		$verifyResponse = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret=' . $secretKey . '&response='.$_POST['g-recaptcha-response']);

		$responseData = json_decode($verifyResponse);

		if($responseData->success) {

Assuming the verification is successful, next is to create the HTML email itself based off the form responses. Kept simple in this example, $email_body is opened and then appended with each field value if they were filled in. Some basic sanitisation on the name and message was originally taking place using FILTER_SANTIZE_STRING, but this is now depreciating as of PHP 8.1.0, and replaced with htmlspecialchars().

$contact_name = "";
$contact_email = "";
$contact_message = "";
$email_body = "<div>";

if(isset($_POST['contact_name'])) {
	$contact_name = htmlspecialchars($_POST['contact_name']);
	$email_body .= "<div><label><strong>Name:</strong></label>&nbsp;<span>" . $contact_name . "</span></div>";
}

if(isset($_POST['contact_email'])) {
	$contact_email = str_replace(array("\r", "\n", "%0a", "%0d"), '', $_POST['contact_email']);
	$contact_email = filter_var($contact_email, FILTER_VALIDATE_EMAIL);
	$email_body .= "<div><label><strong>Email:</strong></label>&nbsp;<span>" . $contact_email . "</span></div>";
}

if(isset($_POST['contact_message'])) {
	$contact_message = htmlspecialchars($_POST['contact_message']);
	$email_body .= "<div><label><strong>Message:</strong></label><div>" . $contact_message . "</div></div>";
}

$email_body .= "</div>";

Now a very quick but important step, setting up a couple of variables for the form submission recipient’s email address (you!) and hard code the subject bar:

$recipient = "[email protected]";
$subject = "Contact Form Submission";

The subject could easily be replaced by another user input on the front-end of the form, but for my use case the recipients filter their incoming emails by subject to put form entries into their own folder to triage.

Now for the last and very important bit, sending the email via SMTP using the email address and password of the Gmail account. Important lines are setting up the $mail as a PHPMailer SMTP, setting the host and port as Gmail and authenticating using your own username and password.

For Gmail the host is smtp.gmail.com and the port is 587.

$mail = new PHPMailer(true);

try {
	// Server settings
	// $mail->SMTPDebug = SMTP::DEBUG_SERVER; // for detailed debug output
	$mail->isSMTP();
	$mail->Host = 'smtp.gmail.com';
	$mail->SMTPAuth = true;
	$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
	$mail->Port = 587;
	$mail->CharSet = "UTF-8";
	$mail->Encoding = 'base64';

	$mail->Username = '[email protected]';
	$mail->Password = 'PASSWORD';

	// Sender and recipient settings
	$mail->setFrom('[email protected]', 'Contact Form');
	$mail->addAddress($recipient);
	$mail->addReplyTo($contact_email, $contact_name);

	// Setting the email content
	$mail->IsHTML(true);
	$mail->Subject = $subject;
	$mail->Body = $email_body;

	$mail->send();

	if($mail) {
		header("location: /contact?status=0"); // success
	} else {
		header("location: /contact?status=1");
	}
} catch (Exception $e) {
	header("location: /contact?status=1");
}

After, finish up closing the if functions opened earlier, with the optional error checking using else where wanted.

		} else {
			header("location: /contact?status=1");
		}
	} else {
		header("location: /contact?status=1");
	}
} else {
	header("location: /contact?status=1");
}

?>

Full form back-end code

<?php

$secretKey = 'SECRETKEY'; 

use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;

require_once $_SERVER['DOCUMENT_ROOT'].'/vendor/phpmailer/src/Exception.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/vendor/phpmailer/src/PHPMailer.php';
require_once $_SERVER['DOCUMENT_ROOT'].'/vendor/phpmailer/src/SMTP.php';

if(isset($_POST['submit'])) {

	if(isset($_POST['g-recaptcha-response']) && !empty($_POST['g-recaptcha-response'])) {

		$verifyResponse = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret=' . $secretKey . '&response='.$_POST['g-recaptcha-response']);

		$responseData = json_decode($verifyResponse);

		if($responseData->success) {

			$contact_name = "";
			$contact_email = "";
			$contact_message = "";
			$email_body = "<div>";

			if(isset($_POST['contact_name'])) {
				$contact_name = htmlspecialchars($_POST['contact_name']);
				$email_body .= "<div><label><strong>Name:</strong></label>&nbsp;<span>" . $contact_name . "</span></div>";
			}

			if(isset($_POST['contact_email'])) {
				$contact_email = str_replace(array("\r", "\n", "%0a", "%0d"), '', $_POST['contact_email']);
				$contact_email = filter_var($contact_email, FILTER_VALIDATE_EMAIL);
				$email_body .= "<div><label><strong>Email:</strong></label>&nbsp;<span>" . $contact_email . "</span></div>";
			}

			if(isset($_POST['contact_message'])) {
				$contact_message = htmlspecialchars($_POST['contact_message']);
				$email_body .= "<div><label><strong>Message:</strong></label><div>" . $contact_message . "</div></div>";
			}

			$email_body .= "</div>";

			$recipient = "[email protected]";
			$subject = "Contact Form Submission";

			$mail = new PHPMailer(true);

			try {
				// Server settings
				// $mail->SMTPDebug = SMTP::DEBUG_SERVER; // for detailed debug output
				$mail->isSMTP();
				$mail->Host = 'smtp.gmail.com';
				$mail->SMTPAuth = true;
				$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
				$mail->Port = 587;
				$mail->CharSet = "UTF-8";
				$mail->Encoding = 'base64';

				$mail->Username = '[email protected]';
				$mail->Password = 'PASSWORD';

				// Sender and recipient settings
				$mail->setFrom('[email protected]', 'Contact Form');
				$mail->addAddress($recipient);
				$mail->addReplyTo($contact_email, $contact_name);

				// Setting the email content
				$mail->IsHTML(true);
				$mail->Subject = $subject;
				$mail->Body = $email_body;

				$mail->send();

				if($mail) {
					header("location: /contact?status=0"); // success
				} else {
					header("location: /contact?status=1");
				}
			} catch (Exception $e) {
				header("location: /contact?status=1");
			}
		} else {
			header("location: /contact?status=1");
		}
	} else {
		header("location: /contact?status=1");
	}
} else {
	header("location: /contact?status=1");
}

?>

Conclusion

An unreliable contact form could have a huge cost for a website’s owner if they missed a potential client trying to engage with them, or cause reputational harm through a lack of response. Relying on shared hosting’s mail servers comes with so many caveats around up-time, server load and the perceived risk of submission emails by recipient email providers. Using Gmail’s SMTP alleviates those, improving deliverability with robust infrastructure behind it, it also lowers the chance of ending up in spam filters, because when the email says it comes from a trusted source it’s because it is literally being authenticated from your Gmail account.

PHPMailer also allows for safe and secure transfer of attaching files and creates reliable formatting and deliverability for HTML emails (which have a reputation for being horrible to design due to Outlook’s limitations being from the early 2000s!). With better debugging, commented out under server settings in the full code example, and error handling, a developer can also easily discover at what point an error is occurring.

Code wise, there’s barely any difference between a mail() form and a PHPMailer one, it’s just a small dependency and setting up the SMTP authentication, but the benefits of using the latter can not be understated.


Resources

I found the follow links very helpful in putting together this PHPMailer form:


Posted

in

,

by

Tags: