File "mail.php"
Full path: /home/dsvchile/public_html/cgi-sys-91f907-20260516211107/mail.php
File
size: 38.8 B
MIME-type: text/x-php
Charset: utf-8
Download Open Edit Advanced Editor Back
<?php
/**
* MailPulse PHP Mailer
* ====================
* Compatible: PHP 5.5+ (5.5, 5.6, 7.x, 8.x)
* No Composer. No extensions beyond standard PHP.
* Upload one file, open in browser, send.
*/
@error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED & ~E_STRICT);
@ini_set('display_errors', '0');
@set_time_limit(0);
@ignore_user_abort(true);
/* ── PHP 5.5-safe helpers ───────────────────────────────────── */
// isset($x) ? $x : $default — replaces ?? operator (PHP 7+)
function mp_post($key, $default = '') {
return isset($_POST[$key]) ? trim($_POST[$key]) : $default;
}
function mp_get($key, $default = '') {
return isset($_GET[$key]) ? trim($_GET[$key]) : $default;
}
// Replaces random_bytes() (PHP 7+)
function mp_rand_bytes($n) {
if (function_exists('openssl_random_pseudo_bytes')) {
return openssl_random_pseudo_bytes($n);
}
$s = '';
for ($i = 0; $i < $n; $i++) { $s .= chr(mt_rand(0, 255)); }
return $s;
}
function mp_hex($n) { return bin2hex(mp_rand_bytes($n)); }
function esc($s) { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
function personalize($tpl, $email, $name) {
$parts = explode('@', $email);
$fallback = $parts[0];
return str_replace(
array('{{email}}', '{{name}}'),
array($email, ($name !== '' ? $name : $fallback)),
$tpl
);
}
function flushOut() {
if (ob_get_level() > 0) { @ob_flush(); }
@flush();
}
function parseRecipients($csv, $text) {
$out = array();
$seen = array();
if (trim($csv) !== '') {
$lines = array_filter(array_map('trim', explode("\n", $csv)));
$first = true;
foreach ($lines as $line) {
$cols = str_getcsv($line);
$email = isset($cols[0]) ? trim($cols[0]) : '';
if ($first && strtolower($email) === 'email') { $first = false; continue; }
$first = false;
if (strpos($email, '@') !== false && !isset($seen[$email])) {
$seen[$email] = true;
$out[] = array('email' => $email, 'name' => (isset($cols[1]) ? trim($cols[1]) : ''));
}
}
}
if (trim($text) !== '') {
foreach (array_filter(array_map('trim', explode("\n", $text))) as $line) {
if (preg_match('/^(.*?)\s*<(.+?)>\s*$/', $line, $m)) {
$email = trim($m[2]);
if (!isset($seen[$email])) {
$seen[$email] = true;
$out[] = array('email' => $email, 'name' => trim($m[1]));
}
} elseif (strpos($line, '@') !== false) {
$email = trim($line);
if (!isset($seen[$email])) {
$seen[$email] = true;
$out[] = array('email' => $email, 'name' => '');
}
}
}
}
return $out;
}
/* ═══════════════════════════════════════════════════════════════
SMTP CLIENT — fsockopen only, PHP 5.5 compatible
═══════════════════════════════════════════════════════════════ */
class SmtpClient {
private $sock = null;
private $log = '';
private $connTmo = 15;
private $cmdTmo = 30;
public function __construct($connTmo = 15, $cmdTmo = 30) {
$this->connTmo = $connTmo;
$this->cmdTmo = $cmdTmo;
}
public function getLog() { return $this->log; }
public function connect($host, $port, $user, $pass, $encryption, $ehlo = '') {
if ($ehlo === '') {
$ehlo = function_exists('gethostname') ? gethostname() : 'localhost';
if (!$ehlo) { $ehlo = 'localhost'; }
}
$errno = 0; $errstr = '';
if ($encryption === 'ssl') {
$this->sock = @fsockopen('ssl://' . $host, $port, $errno, $errstr, $this->connTmo);
} else {
$this->sock = @fsockopen($host, $port, $errno, $errstr, $this->connTmo);
}
if (!$this->sock) {
throw new RuntimeException(
"Cannot connect to {$host}:{$port} — " . $errstr . " (error {$errno}). " .
"Check host/port. Port 25 may be blocked by your host."
);
}
stream_set_timeout($this->sock, $this->cmdTmo);
$this->expect('220');
$this->raw("EHLO {$ehlo}\r\n");
$cap = $this->readAll('250');
if ($encryption === 'tls') {
if (stripos($cap, 'STARTTLS') === false) {
throw new RuntimeException(
'Server does not advertise STARTTLS. ' .
'Try SSL (port 465) or None (port 25).'
);
}
$this->raw("STARTTLS\r\n");
$this->expect('220');
$ok = false;
if (function_exists('stream_socket_enable_crypto')) {
// Try modern TLS constant first; fall back if not defined (PHP 5.5)
if (defined('STREAM_CRYPTO_METHOD_TLS_CLIENT')) {
$ok = @stream_socket_enable_crypto($this->sock, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
}
if (!$ok && defined('STREAM_CRYPTO_METHOD_SSLv23_CLIENT')) {
$ok = @stream_socket_enable_crypto($this->sock, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT);
}
}
if (!$ok) {
throw new RuntimeException(
'STARTTLS handshake failed. ' .
'Try SSL (port 465), or your host may block outbound TLS.'
);
}
$this->raw("EHLO {$ehlo}\r\n");
$this->readAll('250');
}
if ($user !== '') {
$this->raw("AUTH LOGIN\r\n");
$this->expect('334');
$this->raw(base64_encode($user) . "\r\n");
$this->expect('334');
$this->raw(base64_encode($pass) . "\r\n");
$this->expect('235');
}
}
public function send($from, $to, $subject, $html, $text, $returnPath, $extraHeaders) {
$eFrom = $returnPath !== '' ? $returnPath : $this->extractEmail($from);
$eTo = $this->extractEmail($to);
$this->raw("MAIL FROM:<{$eFrom}>\r\n"); $this->expect('250');
$this->raw("RCPT TO:<{$eTo}>\r\n"); $this->expect('250');
$this->raw("DATA\r\n"); $this->expect('354');
$boundary = '----=_MP_' . mp_hex(8);
$msgId = '<' . mp_hex(12) . '@mailpulse.local>';
$date = date('r');
$hdrs = "Date: {$date}\r\n";
$hdrs .= "Message-ID: {$msgId}\r\n";
$hdrs .= "From: {$from}\r\n";
$hdrs .= "To: {$to}\r\n";
$hdrs .= "Subject: =?UTF-8?B?" . base64_encode($subject) . "?=\r\n";
$hdrs .= "MIME-Version: 1.0\r\n";
foreach ($extraHeaders as $k => $v) { $hdrs .= "{$k}: {$v}\r\n"; }
if ($html !== '' && $text !== '') {
$hdrs .= "Content-Type: multipart/alternative; boundary=\"{$boundary}\"\r\n";
$body = "--{$boundary}\r\n"
. "Content-Type: text/plain; charset=UTF-8\r\n"
. "Content-Transfer-Encoding: base64\r\n\r\n"
. chunk_split(base64_encode($text)) . "\r\n"
. "--{$boundary}\r\n"
. "Content-Type: text/html; charset=UTF-8\r\n"
. "Content-Transfer-Encoding: base64\r\n\r\n"
. chunk_split(base64_encode($html)) . "\r\n"
. "--{$boundary}--\r\n";
} elseif ($html !== '') {
$hdrs .= "Content-Type: text/html; charset=UTF-8\r\n"
. "Content-Transfer-Encoding: base64\r\n";
$body = chunk_split(base64_encode($html)) . "\r\n";
} else {
$hdrs .= "Content-Type: text/plain; charset=UTF-8\r\n"
. "Content-Transfer-Encoding: base64\r\n";
$body = chunk_split(base64_encode($text)) . "\r\n";
}
// RFC 5321 dot-stuffing
$raw = $hdrs . "\r\n" . $body;
$raw = preg_replace('/^\.$/m', '..', $raw);
$this->raw($raw . "\r\n.\r\n");
$this->expect('250');
return $msgId;
}
public function quit() {
if ($this->sock) {
@$this->raw("QUIT\r\n");
@fclose($this->sock);
$this->sock = null;
}
}
private function raw($data) {
$preview = strlen($data) > 100 ? substr($data, 0, 80) . "...\n" : $data;
$this->log .= '> ' . $preview;
if (@fwrite($this->sock, $data) === false) {
throw new RuntimeException('Socket write failed — connection dropped');
}
}
private function read() {
$line = @fgets($this->sock, 4096);
if ($line === false) {
throw new RuntimeException('Socket read failed — server closed connection or timeout');
}
$this->log .= '< ' . $line;
return $line;
}
private function readAll($expect) {
$out = '';
do {
$line = $this->read();
$out .= $line;
} while (strlen($line) >= 4 && $line[3] === '-');
if (substr($out, 0, 3) !== $expect) {
throw new RuntimeException("SMTP expected {$expect}, got: " . trim($out));
}
return $out;
}
private function expect($code) {
$line = $this->read();
while (strlen($line) >= 4 && $line[3] === '-') { $line = $this->read(); }
if (substr($line, 0, 3) !== $code) {
throw new RuntimeException("SMTP expected {$code}, got: " . trim($line));
}
return $line;
}
private function extractEmail($addr) {
if (preg_match('/<(.+?)>/', $addr, $m)) { return $m[1]; }
return trim($addr);
}
}
/* ═══════════════════════════════════════════════════════════════
LOCAL SENDER — php mail()
═══════════════════════════════════════════════════════════════ */
function sendViaMail($from, $to, $subject, $html, $text, $extraHeaders) {
$boundary = '----=_MP_' . mp_hex(8);
$msgId = '<' . mp_hex(12) . '@mailpulse.local>';
$addHdrs = "MIME-Version: 1.0\r\n";
$addHdrs .= "From: {$from}\r\n";
$addHdrs .= "Message-ID: {$msgId}\r\n";
$addHdrs .= "Date: " . date('r') . "\r\n";
foreach ($extraHeaders as $k => $v) { $addHdrs .= "{$k}: {$v}\r\n"; }
if ($html !== '' && $text !== '') {
$addHdrs .= "Content-Type: multipart/alternative; boundary=\"{$boundary}\"";
$body = "--{$boundary}\r\n"
. "Content-Type: text/plain; charset=UTF-8\r\n"
. "Content-Transfer-Encoding: base64\r\n\r\n"
. chunk_split(base64_encode($text)) . "\r\n"
. "--{$boundary}\r\n"
. "Content-Type: text/html; charset=UTF-8\r\n"
. "Content-Transfer-Encoding: base64\r\n\r\n"
. chunk_split(base64_encode($html)) . "\r\n"
. "--{$boundary}--";
} elseif ($html !== '') {
$addHdrs .= "Content-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: base64";
$body = chunk_split(base64_encode($html));
} else {
$addHdrs .= "Content-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: base64";
$body = chunk_split(base64_encode($text));
}
$encSubject = '=?UTF-8?B?' . base64_encode($subject) . '?=';
$fromEmail = preg_match('/<(.+?)>/', $from, $m) ? $m[1] : $from;
$ok = @mail($to, $encSubject, $body, $addHdrs, '-f' . $fromEmail);
if (!$ok) {
throw new RuntimeException(
'mail() returned false — server sendmail not configured, ' .
'or your hosting blocks outbound mail() calls. Try Localhost SMTP or External SMTP mode.'
);
}
return $msgId;
}
/* ═══════════════════════════════════════════════════════════════
DIAGNOSTICS PAGE ?check=1
═══════════════════════════════════════════════════════════════ */
if (isset($_GET['check'])) {
header('Content-Type: text/html; charset=utf-8');
// Port checks (short timeout so page loads fast)
$p25 = (bool)@fsockopen('127.0.0.1', 25, $e, $s, 2);
$p587 = (bool)@fsockopen('smtp.gmail.com', 587, $e, $s, 3);
$p465 = (bool)@fsockopen('ssl://smtp.gmail.com', 465, $e, $s, 3);
$chk = array(
'PHP version' => PHP_VERSION . ' (need 5.5+)',
'PHP 5.5+ OK' => version_compare(PHP_VERSION, '5.5.0', '>='),
'fsockopen() — SMTP connection' => function_exists('fsockopen'),
'mail() — local sending' => function_exists('mail'),
'openssl extension' => extension_loaded('openssl'),
'stream_socket_enable_crypto (TLS)' => function_exists('stream_socket_enable_crypto'),
'base64_encode()' => function_exists('base64_encode'),
'File uploads enabled' => (bool)@ini_get('file_uploads'),
'Outbound port 25 (localhost SMTP)' => $p25,
'Outbound port 587 (STARTTLS)' => $p587,
'Outbound port 465 (SSL SMTP)' => $p465,
);
$sendmail = @ini_get('sendmail_path');
$sv = isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : 'unknown';
echo '<!doctype html><html><head><meta charset="utf-8"><title>Server Check</title>
<style>
body{background:#0d0d10;color:#e8e8ee;font-family:Menlo,Consolas,monospace;font-size:13px;padding:30px;line-height:1.9}
h2{color:#22c55e;margin-bottom:16px;font-family:sans-serif;font-size:18px}
.ok{color:#22c55e}.fail{color:#ef4444}.info{color:#888}
table{border-collapse:collapse}td{padding:3px 22px 3px 0}
.note{margin-top:20px;color:#666;font-size:12px;font-family:sans-serif;line-height:1.7}
a{color:#22c55e;display:inline-block;margin-top:20px;font-family:sans-serif;text-decoration:none;border:1px solid #22c55e;padding:6px 14px;border-radius:4px}
</style></head><body>
<h2>🔎 Server Diagnostics</h2><table>';
foreach ($chk as $label => $val) {
if (is_bool($val)) {
$cell = $val ? '<span class="ok">✓ YES</span>' : '<span class="fail">✗ NO</span>';
} else {
$cell = '<span class="info">' . esc($val) . '</span>';
}
echo '<tr><td>' . esc($label) . '</td><td>' . $cell . '</td></tr>';
}
echo '</table>';
echo '<div class="note">';
echo 'Server: ' . esc($sv) . '<br>';
echo 'Sendmail path: ' . esc($sendmail ? $sendmail : 'not configured') . '<br>';
echo '<br>';
echo '<b>What mode to use:</b><br>';
if ($p25) echo '✓ <b>Localhost SMTP</b> — port 25 is open, use this for unlimited sending<br>';
if (!$p25) echo '✗ Localhost SMTP — port 25 is blocked (common on shared hosting)<br>';
if (function_exists('mail')) echo '✓ <b>Local Mail</b> — mail() is available<br>';
if ($p587) echo '✓ <b>External SMTP STARTTLS</b> — port 587 is reachable<br>';
if ($p465) echo '✓ <b>External SMTP SSL</b> — port 465 is reachable<br>';
echo '</div>';
echo '<a href="' . esc($_SERVER['PHP_SELF']) . '">← Back to mailer</a>';
echo '</body></html>';
exit;
}
/* ═══════════════════════════════════════════════════════════════
SEND ACTION
═══════════════════════════════════════════════════════════════ */
if (isset($_POST['action']) && $_POST['action'] === 'send') {
header('Content-Type: text/html; charset=utf-8');
header('X-Accel-Buffering: no');
header('Cache-Control: no-store');
// Read POST (PHP 5.5 safe — no ?? operator)
$mode = mp_post('mode', 'local');
$smtpHost = mp_post('smtp_host', '');
$smtpPort = (int)mp_post('smtp_port', '587');
$smtpUser = mp_post('smtp_user', '');
$smtpPass = mp_post('smtp_pass', '');
$encryption = mp_post('encryption', 'tls');
$ehlo = mp_post('ehlo', '');
$returnPath = mp_post('return_path', '');
$fromName = mp_post('from_name', '');
$fromEmail = mp_post('from_email', '');
$subject = mp_post('subject', '');
$htmlBody = mp_post('html_body', '');
$textBody = mp_post('text_body', '');
$delay = max(0, (int)mp_post('delay_ms', '0'));
$from = ($fromName !== '') ? "{$fromName} <{$fromEmail}>" : $fromEmail;
$extraHdrs = array();
$rawHdrs = mp_post('custom_headers', '');
foreach (explode("\n", $rawHdrs) as $hl) {
$hl = trim($hl);
if (strpos($hl, ':') !== false) {
$parts = explode(':', $hl, 2);
$extraHdrs[trim($parts[0])] = trim($parts[1]);
}
}
$csvText = '';
if (!empty($_FILES['csv_file']['tmp_name']) && is_uploaded_file($_FILES['csv_file']['tmp_name'])) {
$csvText = (string)@file_get_contents($_FILES['csv_file']['tmp_name']);
}
$pasteText = mp_post('recipients_text', '');
$recipients = parseRecipients($csvText, $pasteText);
$total = count($recipients);
$sent = 0; $failed = 0;
$startTs = microtime(true);
echo '<!doctype html><html><head><meta charset="utf-8"><title>Sending…</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{background:#0d0d10;color:#e8e8ee;font-family:Menlo,Consolas,monospace;font-size:13px;padding:20px}
h2{color:#22c55e;margin-bottom:12px;font-family:sans-serif}
#bw{background:#1a1a20;border-radius:4px;height:16px;width:100%;max-width:680px;margin-bottom:10px;overflow:hidden}
#bar{background:#22c55e;height:100%;width:0%}
#stats{color:#888;margin-bottom:14px;font-family:sans-serif;font-size:13px}
.ok{color:#22c55e}.fail{color:#ef4444}.info{color:#666}
#log{line-height:1.9;max-width:920px;word-break:break-all}
a.back{display:inline-block;margin-top:18px;color:#22c55e;text-decoration:none;border:1px solid #22c55e;padding:7px 16px;border-radius:4px;font-family:sans-serif}
details{margin-top:10px}summary{cursor:pointer;color:#555;font-size:11px;font-family:sans-serif}
pre{font-size:11px;background:#0a0a0c;padding:10px;border-radius:4px;overflow:auto;max-height:180px;margin-top:6px;color:#aaa}
</style></head><body>
<h2>✉ Sending bulk emails…</h2>
<div id="bw"><div id="bar"></div></div>
<div id="stats">Preparing ' . $total . ' recipients via <b>' . esc($mode) . '</b> mode…</div>
<div id="log">';
flushOut();
if ($total === 0) {
echo '<span class="fail">✗ No recipients. Go back and add some.</span>';
echo '</div><a class="back" href="' . esc($_SERVER['PHP_SELF']) . '">← Back</a></body></html>';
exit;
}
// Establish connection for SMTP/relay modes
$smtp = null;
if ($mode === 'smtp' || $mode === 'relay') {
$host = ($mode === 'relay') ? '127.0.0.1' : $smtpHost;
$port = ($mode === 'relay') ? 25 : $smtpPort;
$enc = ($mode === 'relay') ? 'none' : $encryption;
$usr = ($mode === 'relay') ? '' : $smtpUser;
$pwd = ($mode === 'relay') ? '' : $smtpPass;
$smtp = new SmtpClient(15, 30);
try {
$smtp->connect($host, $port, $usr, $pwd, $enc, $ehlo);
echo '<span class="info">✓ Connected to ' . esc($host) . ':' . $port . ' (' . esc($enc) . ')</span><br>';
flushOut();
} catch (RuntimeException $e) {
echo '<span class="fail">✗ Connection failed: ' . esc($e->getMessage()) . '</span><br>';
echo '<details open><summary>SMTP log</summary><pre>' . esc($smtp->getLog()) . '</pre></details>';
echo '</div><a class="back" href="' . esc($_SERVER['PHP_SELF']) . '">← Back</a></body></html>';
exit;
}
} else {
echo '<span class="info">Using server mail() — no SMTP connection needed.</span><br>';
flushOut();
}
// Send loop
foreach ($recipients as $i => $r) {
$email = $r['email'];
$name = $r['name'];
$toAddr = ($name !== '') ? "{$name} <{$email}>" : $email;
$subj = personalize($subject, $email, $name);
$html = personalize($htmlBody, $email, $name);
$txt = personalize($textBody, $email, $name);
try {
if ($smtp !== null) {
$smtp->send($from, $toAddr, $subj, $html, $txt, $returnPath, $extraHdrs);
} else {
sendViaMail($from, $toAddr, $subj, $html, $txt, $extraHdrs);
}
$sent++;
$pct = (int)(($i + 1) / $total * 100);
echo '<script>document.getElementById("bar").style.width="' . $pct . '%";'
. 'document.getElementById("stats").textContent="' . ($i+1) . ' / ' . $total
. ' \u2014 Sent: ' . $sent . ' Failed: ' . $failed . '";</script>';
echo '<span class="ok">✓</span> ' . esc($toAddr) . '<br>';
} catch (RuntimeException $e) {
$failed++;
echo '<span class="fail">✗</span> ' . esc($toAddr)
. ' — ' . esc($e->getMessage()) . '<br>';
if ($smtp !== null) {
// Try to reconnect once
try {
$smtp->quit();
$host2 = ($mode === 'relay') ? '127.0.0.1' : $smtpHost;
$port2 = ($mode === 'relay') ? 25 : $smtpPort;
$enc2 = ($mode === 'relay') ? 'none' : $encryption;
$usr2 = ($mode === 'relay') ? '' : $smtpUser;
$pwd2 = ($mode === 'relay') ? '' : $smtpPass;
$smtp = new SmtpClient(15, 30);
$smtp->connect($host2, $port2, $usr2, $pwd2, $enc2, $ehlo);
echo '<span class="info"> → Reconnected, continuing…</span><br>';
} catch (RuntimeException $re) {
echo '<span class="fail"> → Reconnect failed: ' . esc($re->getMessage()) . ' — stopping.</span><br>';
flushOut();
break;
}
}
}
flushOut();
if ($delay > 0) { usleep($delay * 1000); }
}
if ($smtp !== null) { $smtp->quit(); }
$elapsed = round(microtime(true) - $startTs, 1);
echo '<script>document.getElementById("bar").style.width="100%";'
. 'document.getElementById("stats").textContent="Done \u2014 Sent: ' . $sent
. ' Failed: ' . $failed . ' Total: ' . $total . ' Time: ' . $elapsed . 's";</script>';
echo '<br><b style="color:#22c55e">✓ Done!</b>'
. ' Sent: <b>' . $sent . '</b>'
. ' Failed: <b style="color:#ef4444">' . $failed . '</b>'
. ' Total: <b>' . $total . '</b>'
. ' Time: <b>' . $elapsed . 's</b>';
echo '</div><a class="back" href="' . esc($_SERVER['PHP_SELF']) . '">← Send Another</a></body></html>';
exit;
}
/* ═══════════════════════════════════════════════════════════════
MAIN UI
═══════════════════════════════════════════════════════════════ */
$self = esc($_SERVER['PHP_SELF']);
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>MailPulse PHP Mailer</title>
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#0d0d10;--bg2:#141418;--bg3:#1a1a20;--border:#2a2a32;
--fg:#e8e8ee;--fg2:#888899;--green:#22c55e;--red:#ef4444;
--mono:Menlo,Consolas,'Courier New',monospace;
--sans:'Segoe UI',system-ui,Arial,sans-serif;
}
body{background:var(--bg);color:var(--fg);font-family:var(--sans);font-size:14px;min-height:100vh}
.topbar{background:var(--bg2);border-bottom:1px solid var(--border);padding:0 24px;height:52px;display:flex;align-items:center;gap:12px}
.topbar h1{font-size:16px;color:var(--green)}
.topbar .sub{color:var(--fg2);font-size:12px}
.topbar a{color:var(--fg2);font-size:11px;margin-left:auto;text-decoration:none;border:1px solid var(--border);padding:4px 10px;border-radius:4px}
.topbar a:hover{color:var(--fg)}
.layout{display:grid;grid-template-columns:1fr 1fr;max-width:1400px;margin:0 auto;padding:28px 24px;gap:0}
@media(max-width:860px){.layout{grid-template-columns:1fr}}
.col{padding:0 14px}
.col:first-child{padding-left:0;border-right:1px solid var(--border);padding-right:28px}
.col:last-child{padding-left:28px;padding-right:0}
.section{margin-bottom:26px}
.stitle{font-size:11px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--fg2);margin-bottom:14px;padding-bottom:8px;border-bottom:1px solid var(--border)}
label{display:block;font-size:12px;color:var(--fg2);margin-bottom:4px;font-weight:500}
input[type=text],input[type=number],input[type=password],input[type=email],select,textarea{
width:100%;background:var(--bg3);border:1px solid var(--border);color:var(--fg);
font-family:var(--sans);font-size:13px;padding:8px 10px;border-radius:5px;outline:none;transition:border-color .15s}
input:focus,select:focus,textarea:focus{border-color:var(--green)}
textarea{font-family:var(--mono);font-size:12px;resize:vertical}
select option{background:var(--bg2)}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.row3{display:grid;grid-template-columns:2fr 1fr 1fr;gap:12px}
.field{margin-bottom:14px}
.hint{font-size:11px;color:var(--fg2);margin-top:4px}
code{background:var(--bg3);padding:1px 5px;border-radius:3px;font-family:var(--mono);font-size:11px}
.modes{display:flex;gap:8px;margin-bottom:16px;flex-wrap:wrap}
.mode-btn{flex:1;min-width:110px;padding:10px 10px;border:1px solid var(--border);border-radius:6px;background:var(--bg3);color:var(--fg2);cursor:pointer;text-align:center;transition:all .15s;font-family:var(--sans);font-size:13px}
.mode-btn:hover{border-color:var(--fg2);color:var(--fg)}
.mode-btn.active{border-color:var(--green);background:#052e16;color:var(--green)}
.mode-icon{font-size:22px;display:block;margin-bottom:4px}
.mode-name{font-weight:700;font-size:12px;display:block}
.mode-desc{font-size:10px;color:var(--fg2);margin-top:3px;line-height:1.4;display:block}
.mode-btn.active .mode-desc{color:#86efac}
.smtp-panel{display:none}
.smtp-panel.visible{display:block}
.btn{display:flex;align-items:center;justify-content:center;gap:8px;background:var(--green);color:#000;font-weight:700;font-size:14px;border:none;padding:12px;border-radius:6px;cursor:pointer;transition:background .15s;width:100%}
.btn:hover{background:#16a34a}
.btn:disabled{opacity:.5;cursor:not-allowed}
.tabs{display:flex;gap:0;margin-bottom:12px;border-bottom:1px solid var(--border)}
.tab{padding:7px 16px;font-size:12px;cursor:pointer;border-bottom:2px solid transparent;color:var(--fg2);margin-bottom:-1px}
.tab.active{color:var(--green);border-color:var(--green)}
.tabpanel{display:none}.tabpanel.active{display:block}
.dz{border:1px dashed var(--border);border-radius:6px;padding:18px;text-align:center;color:var(--fg2);font-size:12px;cursor:pointer;display:block;margin-bottom:8px}
.dz:hover,.dz.over{border-color:var(--green);color:var(--green)}
.dz input{display:none}
.badge{display:inline-block;background:var(--bg3);border:1px solid var(--border);color:var(--fg2);font-size:11px;padding:2px 8px;border-radius:20px;margin-left:6px}
.badge.g{background:#052e16;border-color:#166534;color:var(--green)}
</style>
</head>
<body>
<div class="topbar">
<h1>✉ MailPulse PHP Mailer</h1>
<span class="sub">PHP 5.5+ • No dependencies • Unlimited sends</span>
<a href="<?php echo $self; ?>?check=1" target="_blank">🔎 Server check</a>
</div>
<form method="post" enctype="multipart/form-data" id="mf" target="_blank">
<input type="hidden" name="action" value="send">
<input type="hidden" name="mode" value="local" id="modeVal">
<div class="layout">
<!-- LEFT ══════════════════════════════════════════════════════════ -->
<div class="col">
<div class="section">
<div class="stitle">Sending Mode</div>
<div class="modes">
<div class="mode-btn active" id="mb-local" onclick="setMode('local')">
<span class="mode-icon">📩</span>
<span class="mode-name">Local Mail</span>
<span class="mode-desc">PHP mail() function<br>Works on all shared hosts</span>
</div>
<div class="mode-btn" id="mb-relay" onclick="setMode('relay')">
<span class="mode-icon">🔁</span>
<span class="mode-name">Localhost SMTP</span>
<span class="mode-desc">127.0.0.1 port 25<br>Server's Postfix / Exim</span>
</div>
<div class="mode-btn" id="mb-smtp" onclick="setMode('smtp')">
<span class="mode-icon">🔒</span>
<span class="mode-name">External SMTP</span>
<span class="mode-desc">Gmail, Outlook, Mailgun<br>Any SMTP with credentials</span>
</div>
</div>
</div>
<div class="section smtp-panel" id="smtpPanel">
<div class="stitle" id="spTitle">SMTP Settings</div>
<div class="row3 field" id="hostRow">
<div>
<label>Host</label>
<input type="text" name="smtp_host" id="smtpHost" placeholder="smtp.gmail.com">
</div>
<div>
<label>Port</label>
<input type="number" name="smtp_port" id="smtpPort" value="587" min="1" max="65535">
</div>
<div>
<label>Encryption</label>
<select name="encryption" id="encSel">
<option value="tls">STARTTLS</option>
<option value="ssl">SSL</option>
<option value="none">None</option>
</select>
</div>
</div>
<div class="row2 field" id="authRow">
<div>
<label>Username</label>
<input type="text" name="smtp_user" placeholder="you@gmail.com">
</div>
<div>
<label>Password / App password</label>
<input type="password" name="smtp_pass">
</div>
</div>
<div class="row2 field">
<div>
<label>EHLO Hostname <small>(optional)</small></label>
<input type="text" name="ehlo" placeholder="mta.yourdomain.com">
</div>
<div>
<label>Return-Path <small>(optional)</small></label>
<input type="text" name="return_path" placeholder="bounce@yourdomain.com">
</div>
</div>
</div>
<div class="section">
<div class="stitle">Sender</div>
<div class="row2 field">
<div><label>From Name</label><input type="text" name="from_name" placeholder="Your Name"></div>
<div><label>From Email</label><input type="email" name="from_email" placeholder="you@domain.com" required></div>
</div>
<div class="field">
<label>Subject</label>
<input type="text" name="subject" placeholder="Hello {{name}}!" required>
<p class="hint">Use <code>{{name}}</code> and <code>{{email}}</code> for per-recipient personalisation.</p>
</div>
</div>
<div class="section">
<div class="stitle">Advanced</div>
<div class="field">
<label>Delay between emails (ms) — 0 = send as fast as possible</label>
<input type="number" name="delay_ms" value="0" min="0" max="60000" style="width:180px">
</div>
<div class="field">
<label>Custom Headers <small>(one per line — Header-Name: value)</small></label>
<textarea name="custom_headers" rows="3" placeholder="X-Campaign: launch-2026 X-Mailer: MailPulse"></textarea>
</div>
</div>
</div><!-- /left -->
<!-- RIGHT ═════════════════════════════════════════════════════════ -->
<div class="col">
<div class="section">
<div class="stitle">Message Body</div>
<div class="tabs" id="bt">
<div class="tab active" data-tp="html">HTML</div>
<div class="tab" data-tp="text">Plain Text</div>
<div class="tab" data-tp="prev">Preview</div>
</div>
<div class="tabpanel active" id="tp-html">
<textarea name="html_body" id="htmlBody" rows="14"
placeholder="<h1>Hello {{name}},</h1> <p>Your email: {{email}}</p> <p>Your message here.</p>"></textarea>
<p class="hint">Inline CSS works best across email clients.</p>
</div>
<div class="tabpanel" id="tp-text">
<textarea name="text_body" rows="14" placeholder="Hello {{name}}, Your email is {{email}}."></textarea>
<p class="hint">Optional plain-text fallback sent alongside HTML.</p>
</div>
<div class="tabpanel" id="tp-prev">
<iframe id="pf" style="width:100%;height:280px;background:#fff;border:1px solid var(--border);border-radius:4px"></iframe>
<p class="hint" style="margin-top:5px">Preview: name=“John”, email=“john@example.com”</p>
</div>
</div>
<div class="section">
<div class="stitle">Recipients <span class="badge" id="rb">0</span></div>
<div class="tabs" id="rt">
<div class="tab active" data-tp="paste">Paste list</div>
<div class="tab" data-tp="upload">CSV file</div>
</div>
<div class="tabpanel active" id="tp-paste">
<textarea name="recipients_text" id="recipTxt" rows="9"
placeholder="alice@example.com Bob Smith <bob@example.com> charlie@example.com"></textarea>
<p class="hint">One per line: <code>email</code> or <code>Name <email></code></p>
</div>
<div class="tabpanel" id="tp-upload">
<label class="dz" id="dz">
<input type="file" name="csv_file" id="cf" accept=".csv,.txt">
<div id="dzTxt">📄 Click to choose CSV — or drag & drop<br>
<span style="font-size:11px">Columns: email, name (header row is optional)</span></div>
</label>
<p class="hint">Example row: <code>alice@example.com, Alice Smith</code></p>
</div>
</div>
<button type="submit" class="btn" id="sb">✉ Send Bulk Emails</button>
<p class="hint" style="text-align:center;margin-top:8px">Opens a new tab with live progress per email.</p>
</div><!-- /right -->
</div><!-- /layout -->
</form>
<script>
function setMode(m) {
document.getElementById('modeVal').value = m;
var ids = ['local','relay','smtp'];
for (var i=0; i<ids.length; i++) {
var el = document.getElementById('mb-'+ids[i]);
if (el) { el.className = 'mode-btn' + (ids[i]===m ? ' active' : ''); }
}
var panel = document.getElementById('smtpPanel');
var hr = document.getElementById('hostRow');
var ar = document.getElementById('authRow');
var title = document.getElementById('spTitle');
if (m === 'local') {
panel.className = 'section smtp-panel';
} else if (m === 'relay') {
panel.className = 'section smtp-panel visible';
hr.style.display = 'none';
ar.style.display = 'none';
title.textContent = 'Localhost SMTP \u2014 connects to 127.0.0.1:25 (no auth)';
} else {
panel.className = 'section smtp-panel visible';
hr.style.display = '';
ar.style.display = '';
title.textContent = 'External SMTP Settings';
}
}
function initTabs(wrapperId) {
var wrap = document.getElementById(wrapperId);
if (!wrap) return;
var tabs = wrap.querySelectorAll('.tab');
for (var i=0; i<tabs.length; i++) {
(function(tab){
tab.addEventListener('click', function() {
var key = tab.getAttribute('data-tp');
for (var j=0; j<tabs.length; j++) { tabs[j].className = 'tab'; }
tab.className = 'tab active';
var section = wrap.parentNode;
var panels = section.querySelectorAll('.tabpanel');
for (var k=0; k<panels.length; k++) {
panels[k].className = panels[k].id === 'tp-'+key ? 'tabpanel active' : 'tabpanel';
}
if (key === 'prev') { updatePreview(); }
});
})(tabs[i]);
}
}
initTabs('bt');
initTabs('rt');
function updatePreview() {
var html = (document.getElementById('htmlBody').value || '')
.replace(/\{\{name\}\}/g, 'John')
.replace(/\{\{email\}\}/g, 'john@example.com');
var doc = document.getElementById('pf').contentDocument;
doc.open(); doc.write(html); doc.close();
}
function countR() {
var lines = (document.getElementById('recipTxt').value || '').split('\n');
var n = 0;
for (var i=0; i<lines.length; i++) { if (lines[i].indexOf('@') !== -1) n++; }
var b = document.getElementById('rb');
b.textContent = n;
b.className = 'badge' + (n ? ' g' : '');
}
document.getElementById('recipTxt').addEventListener('input', countR);
var dz = document.getElementById('dz');
var fi = document.getElementById('cf');
var dt = document.getElementById('dzTxt');
fi.addEventListener('change', function(){ if(fi.files.length) dt.textContent = '\u2713 ' + fi.files[0].name; });
dz.addEventListener('dragover', function(e){ e.preventDefault(); dz.className='dz over'; });
dz.addEventListener('dragleave', function(){ dz.className='dz'; });
dz.addEventListener('drop', function(e){
e.preventDefault(); dz.className='dz';
var f = e.dataTransfer.files[0];
if (!f) return;
try { var dt2=new DataTransfer(); dt2.items.add(f); fi.files=dt2.files; } catch(err){}
dt.textContent = '\u2713 ' + f.name;
});
document.getElementById('mf').addEventListener('submit', function(){
var b = document.getElementById('sb');
b.disabled = true;
b.textContent = 'Sending\u2026 (check the new tab)';
setTimeout(function(){ b.disabled=false; b.innerHTML='\u2709 Send Bulk Emails'; }, 5000);
});
</script>
</body>
</html>