Double Opt-in in PHP with Mailgun

Custom signup form with Mailgun

Creating a signup with Mailgun can be trivial. I receive tons of messages from people asking me to write a tutorial on how to do this in PHP so here it is . Before talking code, let's briefly discuss best practices. If you know everything about double opt-in, skip the next paragraph and move on to the engineering part.

Jump to the code

What is double opt-in and as a developer, why do I care?

A double opt-in process is a way of verifying that when a person leaves their details in your newsletter sign-up form on your website, that they actually did sign for it. It achieves this by asking you to acknowledge and confirm that your email address has been added to a certain mailing list. How does it do that? Simply by sending you an email and asking you to confirm you really want to receive emails from that sender. If you don't confirm your subscription (mostly by clicking on a confirmation link within a confirmation email) you should never receive a marketing (or similar) email from that sender.

Marketing people have learned to dislike this practice as it adds one extra step from reaching their audience, but it is important to understand that without this process anyone could be a target of unsolicited email spam

Without double opt-in, anyone that knows your email address could sign you up to thousands of newsletters and marketing lists on your behalf without your consent.

In most countries this is required by law but either way, I believe it is a best practice and protects your business from stumbling upon deliverability issues and email abuse complaints.

HTML and Javascript Form

The first thing you will want to do is create a form that you will have somewhere on your website. This form will ask people for their details that you can save in a database or if supported, within the email platform itself. Mailgun supports the latter (and even more when using custom variable parameters), so you don't have to worry about having to set up a database to hold this data.

The form will post to a file called contact.php and will have two fields that are mandatory. You can obviously make the name field non mandatory, or add more, but for the purpose of this article, we will keep it simple!


<form action="contact.php" method="POST" onsubmit="return check_the_box()">
    <input name="email" type="text" required placeholder="Your Email">
    <input name="firstName" type="text" required placeholder="Your Name">
    <p>
        <a href="#">I've read and agree to the Terms of Service </a>
        <input id="chx" type="checkbox">
    </p>
    <button type="submit" >Newsletter Signup</button>
    <p>
        <span style="display: none" id="pls"> Please review and tick the Terms of Service box</span>
    </p>
</form>

Using the below Javascript code will ensure the tickbox has been checked before validation (on submit), otherwise we will not pass the email onwards!

<script type="text/javascript">  
    var box = document.getElementById("chx");
    var alrt = document.getElementById("pls");

function check_the_box(){  
    if(!box.checked) {
        alrt.setAttribute("style","display: inline");
            return false
}
else {  
    return true
}
}
</script>  

Feel free to add some style to the form. Right now it's very basic, but functional.

PHP Functions

The interesting part begins here - a file containing all the functions needed to make the form work.

Here is a brief listing of all function created and an explanation of what they do:

  • SanitizeInputs(): This function is used to clean the userinput from any "damaging" characters
  • SanitizeEmail(): Same as above but instead, performs a regex check on the email address, we don't want to send emails to mispelled addresses now do we?
  • SendConfirmationEmail(): Sends an email to a specified email address, in our case it is the email address specified by the user
  • MakeConfirmationHash(): Creates a unique hash based on a string concatenation of the user email's address and a secret string. This string will be sent as part of the URL the user needs to click to verify it wants to receive emails from our site. Secure double opt-in awesomeness.
  • CheckConfirmationHash(): A function checking that when the user clicks on the confirmation link, the hash contained in the URL is valid. This is very important for the double opt-in procedure, otherwise anyone could verify each other's email address.

You can change the text of the email as you wish. Keep in mind that you will be passing the secret hash, the email address and the name of the user back to the script as GET parameters!

Dependency Alert: I've used the official mailgun php library available on GitHub

<?php
require 'vendor/autoload.php';
use Mailgun\Mailgun;

$mgClient = new Mailgun('key-UseYourOwnAPIKeyHere');
$domain = 'mydomain.com'; // Use your domain
$uniqueKey = 'thisisalongstringnooneknowsabout'; //Any string will work 
$mailingList = 'me@mydomain.com'; // Create a MailingList in the control Panel first
$siteURL = 'http://mysite.com/mailgun';


function SanitizeInputs($var) {
    $sane =  htmlspecialchars($var, ENT_QUOTES);
    return $sane;

}

function SanitizeEmail ($var) {
    $sane =  htmlspecialchars($var, ENT_QUOTES);
    $pattern = "/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/";
    preg_match($pattern, $sane, $res);
    $r = $res[0] ?  $res[0] :  false;
    return $r;
}

function SendConfirmationEmail($recipientAddress, $subjectText, $recipientName) {

    global $mgClient, $domain, $uniqueKey;

    $hashedUnique = MakeConfirmationHash($recipientAddress, $uniqueKey);

  $result = $mgClient->sendMessage($domain, array(
  'from'    => 'confirmation@'.$domain,
  'to'      => $recipientAddress,
  'subject' => $subjectText,
  'html'    => '<p>Hello, please confirm you want to receive marketing emails from '.$domain.' by clicking on the below link <p><a href="$siteURL/contact.php?c='.$hashedUnique.'&e='.$recipientAddress.'&n='.$recipientName.'">CONFIRMATION</a></p></p><p> This email was sent to'.$recipientName.', at the speed of a bullet, by Mailgun!</a>'
  ));
}


 function MakeConfirmationHash($confEmail, $confCode) {
    return md5($confEmail.$confCode);
 }

 function CheckConfirmationHash($confEmail, $confCode) {
    global $uniqueKey;
    if (md5($confEmail.$uniqueKey) === $confCode) { return TRUE; }
        else { return FALSE; }
 }

function AddMemberToList($recipientAddress,$recipientName) {
    global $mailingList,$mgClient;
    $result = $mgClient->post("lists/$mailingList/members", array(
    'address'     => $recipientAddress,
    'name'        => $recipientName,
    'description' => 'Form Opt In',
    'subscribed'  => true    
));
}

PHP Logic

For the logic section, I'll explain the reasoning behind the code.

When the user enters his email address and name inside the box and submits the form, the following happens:

  1. The script generates a unique code and emails it to the user
  2. The user receives an email with a confirmation link, containing information about him and unique code: Clicking on the email will confirm he is okay with his address being part of our mailing list.
  3. Clicking the URL sends back details of the user who are checked against the unique code and triggers his addition to the mailing list on Mailgun.

And that's it, double-opt in respected, a well job done!

<?php

require("functions.php");


if (isset($_POST['email']) && isset($_POST['firstName'])) { 
    $email = SanitizeEmail($_POST['email']);
    $name = SanitizeInputs($_POST['firstName']);

    SendConfirmationEmail($email,"Email Subscription Confirmation",$name);

    echo "Email Sent to your email! Check your inbox and confirm it! Thank you";
}

$c = isset($_GET['c']) ? SanitizeInputs($_GET['c']) : NULL;
$e = isset($_GET['e']) ? SanitizeInputs($_GET['e']) : NULL;
$n = isset($_GET['n']) ? SanitizeInputs($_GET['n']) : NULL;

if (isset($c) && isset($e)&& isset($n) ) {
        $a = CheckConfirmationHash($e, $c);
        if ($a) {
            echo "Success! Thanks for signing up ";
            AddMemberToList($e,$n);
        }
        else {
            echo "Oh Noes! Confirmation went wrong! Did you click on the right link?";
        }

}


?>

Let me know if you've enjoyed this article by sharing your thoughts in the comment box below or on Twitter; and if you'd love to see this tutorial in a different programming language, please let me know.

Mail you later,

comments powered by Disqus

Mailgun Stay in Touch

Get new posts delivered straight to your inbox.