Email Newsletter Subscription System with PHP & MySQL

A newsletter subscription is the most used feature on the website or blog. The email subscription functionality provides an option for the visitor to receive updates via email. The email subscription feature is the most important part of email marketing. Most of the website provides an email subscription option that allows the site visitors to get updates daily or weekly newsletter from the website.

An email newsletter subscription system allows website visitors to subscribe using their email addresses. Once subscribed, they receive an email confirmation link to verify their address. This helps you build a genuine, spam-free mailing list for updates, offers, or new articles.

In this tutorial, you’ll learn how to create a complete email newsletter subscription system using PHP and MySQL, including database design, form validation, and email verification.

πŸš€ Tutorial Overview

– Create a MySQL database and table to store subscriber information.
– Build an HTML subscription form with client-side validation using JavaScript.
– Implement server-side validation and handle form submissions with PHP.
– Send verification emails to subscribers using PHP’s mail function.
– Verify email addresses through a unique link sent to the subscriber’s email.

Features:

  • Responsive subscription form with HTML and CSS.
  • Client-side validation for name and email using JavaScript.
  • Server-side validation and sanitization with PHP.
  • MySQL database integration to store subscriber data.
  • Email verification system to confirm subscriptions.
  • AJAX form submission for a seamless user experience.
  • Secure form handling with validation to prevent SQL injection and ensure data integrity.
  • User-friendly feedback messages for successful subscriptions and errors.

Technologies Used:

  • HTML5 and CSS3 for the subscription form design.
  • JavaScript for client-side validation and AJAX requests.
  • PHP for server-side processing and email handling.
  • MySQL for database management and storing subscriber information.

πŸ“ Folder Structure

email_newsletter_subscription/
β”œβ”€β”€ config.php
β”œβ”€β”€ db.php
β”œβ”€β”€ index.html
β”œβ”€β”€ subscribe.php
β”œβ”€β”€ verify.php
└── css/
    └── style.css

βš™οΈ Step 1: Create the Database and Table

First, create a MySQL database (e.g., newsletter_db) and a table (e.g., subscriptions) to store the subscriber information. You can use phpMyAdmin or any MySQL client to run the following SQL commands:

CREATE TABLE `subscriptions` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) NOT NULL,
  `email` VARCHAR(255) NOT NULL,
  `token` VARCHAR(64) DEFAULT NULL,
  `is_verified` TINYINT(1) NOT NULL DEFAULT 0,
  `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
  `verified_at` DATETIME DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email_unique` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

βš™οΈ Step 2: Database & Mail Configuration

Create a config.php file to store your database and mail configuration settings. Update the placeholders with your actual database credentials (db_host, db_user, db_pass, and db_name) and mail settings (mail_from and mail_from_name).

<?php 
// Configuration for database and mail
// IMPORTANT: Update these values for your environment
return [
    
// Database (MySQL) settings
    
'db_host' => 'localhost',
    
'db_user' => 'root',
    
'db_pass' => 'root_pass',
    
'db_name' => 'newsletter_db',

    
// From email used when sending verification email
    
'mail_from' => 'no-reply@example.com',
    
'mail_from_name' => 'Your Company',
];
?>

βš™οΈ Step 3: Create Database Connection

Create a db.php file to establish a connection to the MySQL database using MySQLi.

<?php 
// Load configuration file and get DB settings
$config = require __DIR__ '/config.php';

// Create MySQLi connection
$mysqli = new mysqli($config['db_host'], $config['db_user'], $config['db_pass'], $config['db_name']);

// Check connection
if ($mysqli->connect_errno) {
    
// avoid exposing internals to the client; log and return a generic message
    
error_log('MySQL connect error: ' $mysqli->connect_error);
    
http_response_code(500);
    die(
'Database connection failed.');
}

$mysqli->set_charset('utf8mb4');
?>

βš™οΈ Step 4: Create the Subscription Form

Create an index.html file with the subscription form and client-side validation using JavaScript and AJAX.

  • Define the HTML structure for the subscription form.
  • Add input fields for name and email with appropriate labels and placeholders.
  • Include a section to display feedback messages to the user.
  • Implement JavaScript to handle form submission, perform client-side validation, and send data to the server-side handler script (subscribe.php) using AJAX.
<!-- subscription form -->
<form id="subscribeForm" novalidate>
    <div id="form-messages" class="messages" aria-live="polite"></div>

    <label for="name">Name</label>
    <input type="text" id="name" name="name" placeholder="Your full name" required>

    <label for="email">Email</label>
    <input type="email" id="email" name="email" placeholder="you@example.com" required>

    <button type="submit" id="submitBtn">Subscribe</button>
</form>

<!-- JavaScript and AJAX -->
<script>
(function(){
    const form = document.getElementById('subscribeForm');
    const messages = document.getElementById('form-messages');
    const submitBtn = document.getElementById('submitBtn');

    function showMessage(html, type='info'){
        messages.innerHTML = `<div class="msg ${type}">${html}</div>`;
    }

    function clearMessage(){ messages.innerHTML = ''; }

    function validateClient(name, email){
        const errors = [];
        const n = name.trim();
        const e = email.trim();
        if(n.length < 2) errors.push('Name must be at least 2 characters.');
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if(!emailRegex.test(e)) errors.push('Please enter a valid email address.');
        return errors;
    }

    form.addEventListener('submit', async (ev) => {
        ev.preventDefault();
        clearMessage();
        submitBtn.disabled = true;

        const name = form.name.value || '';
        const email = form.email.value || '';

        const errors = validateClient(name, email);
        if(errors.length){
            showMessage(errors.map(e => `<div>${e}</div>`).join(''), 'error');
            submitBtn.disabled = false;
            return;
        }

        const data = new FormData();
        data.append('name', name);
        data.append('email', email);

        try{
            const res = await fetch('subscribe.php', { method: 'POST', body: data });
            const json = await res.json();
            if(json.success){
                showMessage(json.message, 'success');
                form.reset();
            } else {
                // validation or server errors (array or string)
                if(json.errors && Array.isArray(json.errors)){
                    showMessage(json.errors.map(e => `<div>${e}</div>`).join(''), 'error');
                } else if(json.message){
                    showMessage(json.message, 'error');
                } else {
                    showMessage('An unexpected error occurred.', 'error');
                }
            }
        } catch(err){
            showMessage('Network error. Please try again later.', 'error');
        } finally {
            submitBtn.disabled = false;
        }
    });
})();
</script>

βš™οΈ Step 5: Handle Subscription Request and Send Verification Email

Create a subscribe.php file to handle the form submission, perform server-side validation, store subscriber data in the database (MySQL), and send a verification email.

  • Load the configuration and database connection files.
  • Retrieve and sanitize the form data (name and email).
  • Perform server-side validation to ensure the name and email are valid.
  • Check if the email is already subscribed.
  • If not subscribed, generate a unique verification token and store the subscriber data in the database.
  • Send a verification email to the subscriber with a unique link containing the token (verify.php) using PHP mail() function.
  • Return a JSON response indicating success or failure.
<?php 
// set response type to JSON
header('Content-Type: application/json; charset=utf-8');

// only accept POST requests
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    
http_response_code(405);
    echo 
json_encode(['success' => false'message' => 'Method not allowed']);
    exit;
}

// load config and database connection
require_once __DIR__ '/db.php';

// get input from POST data
$name = isset($_POST['name']) ? trim($_POST['name']) : '';
$email = isset($_POST['email']) ? trim($_POST['email']) : '';

$errors = [];
// server-side validation
if (mb_strlen($name) < 2) {
    
$errors[] = 'Name must be at least 2 characters.';
}
if (!
filter_var($emailFILTER_VALIDATE_EMAIL)) {
    
$errors[] = 'Please provide a valid email address.';
}

// if validation errors, return
if (!empty($errors)) {
    echo 
json_encode(['success' => false'errors' => $errors]);
    exit;
}

// sanitize for storage
$name_for_db mb_substr(strip_tags($name), 0255);
$email_for_db mb_substr($email0255);

// check if email already exists
$stmt $mysqli->prepare('SELECT id, is_verified FROM subscriptions WHERE email = ? LIMIT 1');
if (!
$stmt) {
    
error_log('Prepare failed: ' $mysqli->error);
    echo 
json_encode(['success' => false'message' => 'Server error.']);
    exit;
}
$stmt->bind_param('s'$email_for_db);
$stmt->execute();
$stmt->bind_result($existing_id$existing_verified);
if (
$stmt->fetch()) {
    
$stmt->close();
    if (
$existing_verified == 1) {
        echo 
json_encode(['success' => false'message' => 'This email is already subscribed and verified.']);
        exit;
    }
    
// existing but not verified: we will re-send verification
} else {
    
$stmt->close();
}

// create or update token
try {
    
$token bin2hex(random_bytes(16));
} catch (
Exception $e) {
    
$token bin2hex(openssl_random_pseudo_bytes(16));
}

// insert or update subscription
if (isset($existing_id) && $existing_id) {
    
// update token and name
    
$up $mysqli->prepare('UPDATE subscriptions SET name = ?, token = ?, created_at = NOW() WHERE id = ?');
    
$up->bind_param('ssi'$name_for_db$token$existing_id);
    
$ok $up->execute();
    
$up->close();
} else {
    
// insert new subscription
    
$ins $mysqli->prepare('INSERT INTO subscriptions (name, email, token, is_verified, created_at) VALUES (?, ?, ?, 0, NOW())');
    if (!
$ins) {
        
error_log('Insert prepare failed: ' $mysqli->error);
        echo 
json_encode(['success' => false'message' => 'Server error.']);
        exit;
    }
    
$ins->bind_param('sss'$name_for_db$email_for_db$token);
    
$ok $ins->execute();
    
$ins->close();
}

// check if DB operation was successful
if (empty($ok)) {
    
error_log('Insert/update failed: ' $mysqli->error);
    echo 
json_encode(['success' => false'message' => 'Could not save subscription.']);
    exit;
}

// build verification link
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' 'http';
$host $_SERVER['HTTP_HOST'];
$path rtrim(dirname($_SERVER['PHP_SELF']), '/\\');
$verifyUrl $protocol '://' $host $path '/verify.php?token=' urlencode($token);

// professional HTML email template (simple, clean)
$subject 'Confirm your subscription';

$message "<!doctype html>
<html>
<head>
  <meta charset=\"utf-8\"> 
  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"> 
  <style>body{font-family:Arial,Helvetica,sans-serif;color:#111} a.button{display:inline-block;padding:12px 18px;background:#1366d6;color:#fff;border-radius:6px;text-decoration:none}</style>
</head>
<body>
  <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" role=\"presentation\">
    <tr><td align=\"center\">
      <table width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"background:#ffffff;border-radius:8px;padding:24px;\" role=\"presentation\">
        <tr><td>
          <h2 style=\"color:#123;\">Confirm your email address</h2>
          <p>Hi " 
htmlspecialchars($name_for_dbENT_QUOTES ENT_HTML5'UTF-8') . ",</p>
          <p>Thank you for subscribing to our newsletter. Please click the button below to confirm your subscription and start receiving updates.</p>
          <p style=\"text-align:center;\"><a class=\"button\" href=\"" 
$verifyUrl "\">Confirm my subscription</a></p>
          <p>If the button doesn't work, copy and paste the following URL into your browser:</p>
          <p style=\"word-break:break-all;\"><a href=\"" 
$verifyUrl "\">" $verifyUrl "</a></p>
          <hr>
          <p style=\"font-size:0.9rem;color:#666;\">If you did not request this email, you can safely ignore it.</p>
        </td></tr>
      </table>
    </td></tr>
  </table>
</body>
</html>"
;

$headers "MIME-Version: 1.0\r\n";
$headers .= "Content-type: text/html; charset=utf-8\r\n";
$headers .= "From: " addslashes($config['mail_from_name']) . " <" $config['mail_from'] . ">\r\n";

// send email
$mailSent = @mail($email_for_db$subject$message$headers);

if (
$mailSent) {
    
// return success
    
echo json_encode(['success' => true'message' => 'Subscription saved. A verification email has been sent to your inbox.']);
} else {
    
// still a success from DB perspective, but warn user that delivery may fail
    
echo json_encode(['success' => true'message' => 'Subscription saved. We could not send the verification email automatically; please contact support.']);
}
?>

βš™οΈ Step 6: Verify Email Address

Create a verify.php file to handle email verification. This file will check the verification token sent to the user’s email and activate their account.

  • Get the token from the query parameter.
  • Check if the token exists in the database.
  • If valid, update the user’s status to verified.
  • Display a success or error message based on the verification result.
<?php 
// include database connection
require_once __DIR__ '/db.php';

// get token from query parameter
$token = isset($_GET['token']) ? trim($_GET['token']) : '';
?> <!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>Subscription Verification - CodexWorld</title> <!-- stylesheet file --> <link rel="stylesheet" href="css/style.css"> </head> <body> <main class="container">     <?php
    
// validate token
    
if (empty($token)) {
        echo 
'<h1>Invalid verification link</h1><p class="msg error">No token provided.</p>';
        exit;
    }

    
// look up subscription by token
    
$stmt $mysqli->prepare('SELECT id, email, name, is_verified FROM subscriptions WHERE token = ? LIMIT 1');
    if (!
$stmt) {
        echo 
'<h1>Server error</h1><p class="msg error">Please try again later.</p>';
        exit;
    }
    
$stmt->bind_param('s'$token);
    
$stmt->execute();
    
$stmt->bind_result($id$email$name$is_verified);
    if (!
$stmt->fetch()) {
        echo 
'<h1>Invalid or expired link</h1><p class="msg error">We could not find a matching subscription.</p>';
        
$stmt->close();
        exit;
    }
    
$stmt->close();

    
// check if already verified
    
if ($is_verified == 1) {
        echo 
'<h1>Already verified</h1><p class="msg info">This email (' htmlspecialchars($email) . ') has already been verified.</p>';
        exit;
    }

    
// update database to set verified
    
$up $mysqli->prepare('UPDATE subscriptions SET is_verified = 1, verified_at = NOW() WHERE id = ?');
    if (!
$up) {
        echo 
'<h1>Server error</h1><p class="msg error">Could not verify at this time.</p>';
        exit;
    }
    
$up->bind_param('i'$id);
    
$ok $up->execute();
    
$up->close();

    
// show result
    
if ($ok) {
        echo 
'<h1>Subscription confirmed</h1><p class="msg success">Thank you, ' htmlspecialchars($name) . '. Your email (' htmlspecialchars($email) . ') has been verified. You will now receive our newsletter.</p>';
    } else {
        echo 
'<h1>Verification failed</h1><p class="msg error">Could not update verification status. Please try again later.</p>';
    }
    
?> </main> </body> </html>

πŸ›‘οΈ Security and Best Practices

  • Always sanitize and validate user inputs to prevent SQL injection and XSS attacks.
  • Use prepared statements (PDO or MySQLi) for database queries to prevent SQL injection.
  • Validate email format using filter_var($email, FILTER_VALIDATE_EMAIL).
  • Use SMTP for sending emails to ensure reliable delivery (consider using libraries like PHPMailer).
  • Add CAPTCHA (like Google reCAPTCHA) to your subscription form to prevent bot sign-ups.
  • Implement error handling to manage exceptions gracefully.
  • Consider using HTTPS to secure data transmission.

πŸŽ‰ Conclusion

With this tutorial, you’ve built a complete email newsletter subscription system with PHP and MySQL, including email verification. You can now connect this system to email marketing platforms or use it to send periodic newsletters manually.

Feel free to expand upon this basic system by adding features like unsubscribe functionality, user management, or analytics. Happy coding! πŸš€

Looking for expert assistance to implement or extend this script’s functionality? Submit a Service Request

9 Comments

  1. Aris Said...
  2. PDG Said...
  3. PDG Said...
    • CodexWorld Said...
  4. Saran Said...
  5. Melkamu Said...
  6. Elisecross Said...
  7. Soha Said...
  8. Peter Said...

Leave a reply

construction Need this implemented in your project? Request Implementation Help β†’ keyboard_double_arrow_up