Once upon a time, a student created a “mass email sender PHP script” using AJAX long polling. Well, it works. But Master Coffee couldn’t help but laugh at the “browser must be permanently open on the server” design… There are better ways to do it, and here’s a quick and simple example. Let’s go!
CODE DOWNLOAD
I have released this under the MIT license, feel free to use it in your own project – Personal or commercial. Some form of credits will be nice though. 🙂
VIDEO TUTORIAL
PART 1) THE DATABASE
CREATE TABLE `queue` (
`campaign` bigint(20) NOT NULL,
`email` varchar(255) NOT NULL,
`timestamp` datetime DEFAULT NULL,
`status` tinyint(1) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_swedish_ci;
ALTER TABLE `queue`
ADD PRIMARY KEY (`campaign`,`email`),
ADD KEY `timestamp` (`timestamp`),
ADD KEY `status` (`status`);
INSERT INTO `queue` (`campaign`, `email`, `timestamp`, `status`) VALUES
(1, '[email protected]', NULL, NULL),
(1, '[email protected]', NULL, NULL),
(1, '[email protected]', NULL, NULL),
(1, '[email protected]', NULL, NULL),
(1, '[email protected]', NULL, NULL),
(1, '[email protected]', NULL, NULL),
(1, '[email protected]', NULL, NULL),
(1, '[email protected]', NULL, NULL),
(1, '[email protected]', NULL, NULL),
(1, '[email protected]', NULL, NULL),
(1, '[email protected]', NULL, NULL),
(1, '[email protected]', NULL, NULL);
First, create an “email send queue and delivery status” table. Here’s a simple one.
campaign
Primary key, campaign ID.email
Primary key, the subscriber’s email.timestamp
Date/time when the email is processed.status
Status of email delivery.NULL
Not processed yet.1
Successfully sent.2
Send error.
PART 2) PHP EMAIL SENDER DAEMON
<?php
// (PART A) SETTINGS
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHAR", "utf8mb4");
define("DB_USER", "root");
define("DB_PASS", "");
define("DB_ERROR", PDO::ERRMODE_EXCEPTION);
define("DB_FETCH", PDO::FETCH_ASSOC);
define("CYCLE_EACH", 5); // send 5 emails every batch
define("CYCLE_BATCH", 1); // pause 1 second before sending next email batch
define("CYCLE_CHECK", 5); // pause 5 seconds before next cycle
// (PART B) MYSQL DATABASE CLASS
class DB {
// (B1) CONNECT TO DATABASE
public $error = null;
private $pdo = null;
private $stmt = null;
function __construct () {
$this->pdo = new PDO(
"mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHAR,
DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => DB_ERROR,
PDO::ATTR_DEFAULT_FETCH_MODE => DB_FETCH
]);
}
// (B2) CLOSE DATABASE CONNECTION
function __destruct () {
if ($this->stmt !== null) { $this->stmt = null; }
if ($this->pdo !== null) { $this->pdo = null; }
}
// (B3) RUN SQL QUERY
function query ($sql, $data=null) : void {
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($data);
}
// (B4) FETCH
function fetch ($sql, $data=null) {
$this->query($sql, $data);
return $this->stmt->fetchAll();
}
}
// (PART C) ENDLESS LOOP
while (true) {
// (C1) GET QUEUE & SEND EMAIL
$db = new DB();
$batch = [];
do {
// (C1-1) GET EMAILS
$batch = $db->fetch(
"SELECT `campaign`, `email`
FROM `queue`
WHERE `status` IS NULL
LIMIT " . CYCLE_EACH
);
// (C1-2) SEND EMAIL
if (count($batch)>0) {
foreach ($batch as $b) {
$ok = @mail($b["email"], "Title", "Message");
$db->query(
"UPDATE `queue` SET `timestamp`=?, `status`=? WHERE `campaign`=? AND `email`=?",
[date("Y-m-d H:i:s"), $ok ? 1 : 0, $b["campaign"], $b["email"]]
);
}
sleep(CYCLE_BATCH);
}
} while (count($batch)>0);
// (C2) NEXT CYCLE
unset($db);
sleep(CYCLE_CHECK);
}
- Database and “email batch processing” settings – Remember to change these to your own.
- A simple class library to work with the database.
- Yes, the “mass mailer” is essentially an endless loop.
- (C1) Connect to the database, get the emails to send, and loop until the queue is empty.
- (C1) For this example – We send 5 emails every batch and wait for 1 second before sending the next batch.
- (C2) When the queue is empty, the endless loop turns into “passive mode”. Check the queue every 5 seconds.
PART 3) LAUNCH EMAIL DAEMON IN THE COMMAND LINE
Finally, launch php 2-send.php
in the command line. The smarter way is to run this on system startup, we will not walk through the details, but a quick summary:
- (Windows) Run
shell:startup
, create anemail.bat
file with one line –php PATH/TO/2-send.php
. - (Linux) Similarly, create an
email.sh
file –php PATH/TO/2-send.php
. But depending on your distro, setting it to run at startup will be different – Check out this guide. - (Mac) Do your own research. 😛
THE END – POSSIBLE IMPROVEMENTS
That’s all for this short tutorial and sharing. Before you deploy this on a live production server, there are plenty of things to complete on your own.
- Go ahead and increase
CYCLE_EACH
. Modern servers are capable of handling way more than 5 emails per second… Just don’t flood and crash your SMTP server. - Tie in with your newsletter system.
- Load the email template from a flat file or the database – Check out my PHP email template tutorial if you want.
- For simplicity, I have used the default PHP
mail()
. Go ahead and use PHPMailer if you want. - Add “priority” to the queue, and maybe a “resend” function.
- The slightly more advanced folks might want to check out ReactPHP – Turn this daemon into an API endpoint or worker.
CHEAT SHEET