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.
– 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:
Technologies Used:
email_newsletter_subscription/ βββ config.php βββ db.php βββ index.html βββ subscribe.php βββ verify.php βββ css/ βββ style.css
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;
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',
];
?>
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');
?>
Create an index.html file with the subscription form and client-side validation using JavaScript and AJAX.
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>
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.
verify.php) using PHP mail() function.<?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($email, FILTER_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), 0, 255);
$email_for_db = mb_substr($email, 0, 255);
// 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_db, ENT_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.']);
}
?>
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.
verified.<?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>
filter_var($email, FILTER_VALIDATE_EMAIL).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
π° Budget-friendly β’ π Global clients β’ π Production-ready solutions
I think the app you created will help me a lot.
I have read the instructions and thank you for sending them to me.
Let me put you where I am at:
1. I have created SQL/subscribers.sql as you suggested.
2. In the config.php:
— in “Site Settings” I filled in the name of the website that will contain the newsletter (subscription form). I didn’t right?
— in “Google reCAPTCHA API key settings” I added the sitekey and secret key.
— in “Database Configuration” I added its details: server, password, etc.
3. Now, what should I do with the files you sent me?
Do I need to uploada any or all of them on the server?
Thank you.
Purchased it and is saving subscriber details in the database.
How to send emails to these subscribers?
Does this work with php 8?
Yes, this script is compatible with PHP 8.
For me Working Good.
Can you provide style.css file
Thank you
thank you
ok good
thanks very much.
that is great!
Hi,
Could you upload all files of newsletter it can be helpfull.
including with structure base to import it by myself
Thank you