Отправка почты по шаблону минуя SMTP-сервер провайдера на PHP
Пример по созданию и отправке письма на основании активного шаблона письма минуя SMTP-сервер провайдера.
// Посылка почты вручную, минуя SMTP-сервер провайдера.
include_once "lib/getmxrr.php";
include_once "lib/template.php";
include_once "lib/mailenc.php";
include_once "lib/mailx.php";
include_once "lib/socketmail.php";
$mail = template("mail.php.eml", array(
"to" => "Кто-то очень страшный <somebody@thematrix.com>",
"text" => "Проверка слуха!"
));
$mail = mailenc($mail);
mailx_manual($mail, $reason)
or die("Errors:<br>".join("<br>", $reason));
echo "Почта отослана.";
lib/getmxrr.php - эмуляция функции getmxrr(), которая не работает на Windows хостинге
if (!function_exists("getmxrr")) {
function getmxrr($hostname, &$hosts, &$weights=false) {
$hosts = $weights = [];
// Не идеальный способ, но работающий: используется внешняя
// программа nslookup, доступная в WIndows NT/2000/XP/2003.
exec("nslookup -type=mx $hostname", $result);
// Построчно перебираем ответ утилиты.
foreach ($result as $line) {
// Выделяем имя почтового сервера.
if (preg_match('/mail\s+exchanger\s*=\s*(\S+)/', $line, $pock)) {
$hosts[] = $pock[1];
// Также выделяем вес.
if (preg_match("/MX\s+preference\s*=\s*(\d+)/", $line, $pock))
$weights[] = $pock[1];
else
$weights[] = 0;
}
}
return count($hosts) > 0;
}
}
// В PHP5 появился синоним для getmxrr() - его мы тоже эмулируем.
if (!function_exists("dns_get_mx")) {
function dns_get_mx($hostname, &$hosts, &$weights) {
return getmxrr($hostname, $hosts, $weights);
}
}
lib/template.php - обработка шаблона
// Функция делает то же самое, что инструкция include, однако
// блокирует вывод текста в браузер. Вместо этого текст возвращается
// в качестве результата. Функцию можно использовать, например,
// для обработки почтовых шаблонов.
function template($__fname, $vars) {
// Перехватываем выходной поток.
ob_start();
// Запускаем файл как программу на PHP.
extract($vars, EXTR_OVERWRITE);
include($__fname);
// Получаем перехваченный текст.
$text = ob_get_contents();
ob_end_clean();
return $text;
}
lib/mailx.php
// Функция отправляет письмо, полностью заданное в параметре $mail.
// Корректно обрабатываются заголовки To и Subject.
function mailx($mail) {
// Разделяем тело сообщения и заголовки.
list ($head, $body) = preg_split("/\r?\n\r?\n/s", $mail, 2);
// Выделяем заголовок To.
$to = "";
if (preg_match('/^To:\s*([^\r\n]*)[\r\n]*/m', $head, $p)) {
$to = @$p[1]; // сохраняем
$head = str_replace($p[0], "", $head); // удаляем из исходной строки
}
// Выделяем Subject.
$subject = "";
if (preg_match('/^Subject:\s*([^\r\n]*)[\r\n]*/m', $head, $p)) {
$subject = @$p[1];
$head = str_replace($p[0], "", $head);
}
// Отправляем почту. Внимание! Опасный прием!
mail($to, $subject, $body, trim($head));
}
lib/mailenc.php
// Корректно кодирует все заголовки в письме $mail с использованием
// метода base64. Кодировка письма определяется автоматически на основе
// заголовка Content-type. Возвращает полученное письмо.
function mailenc($mail) {
// Разделяем тело сообщения и заголовки.
list ($head, $body) = preg_split("/\r?\n\r?\n/s", $mail, 2);
// Определяем кодировку письма по заголовку Content-type.
$encoding = '';
$re = '/^Content-type:\s*\S+\s*;\s*charset\s*=\s*(\S+)/mi';
if (preg_match($re, $head, $p)) $encoding = $p[1];
// Проходимся по всем строкам-заголовкам.
$newhead = "";
foreach (preg_split('/\r?\n/s', $head) as $line) {
// Кодируем очередной заголовок.
$line = mailenc_header($line, $encoding);
$newhead .= "$line\r\n";
}
// Формируем окончательный результат.
return "$newhead\r\n$body";
}
// Кодирует в строке максимально возможную последовательность
// символов, начинающуюся с недопустимого символа и НЕ
// включающую E-mail (адреса E-mail обрамляют символами < и >).
// Если в строке нет ни одного недопустимого символа, преобразование
// не производится.
function mailenc_header($header, $encoding) {
// Кодировка не задана - делать нечего.
if (!$encoding) return $header;
// Сохраняем кодировку в глобальной переменной. Без использования
// ООП это - единственный способ передать дополнительный параметр
// callback-функции.
$GLOBALS['mail_enc_header_encoding'] = $encoding;
return preg_replace_callback(
'/([\x7F-\xFF][^<>\r\n]*)/s',
'mailenc_header_callback',
$header
);
}
// Служебная функция для использования в preg_replace_callback().
function mailenc_header_callback($p) {
$encoding = $GLOBALS['mail_enc_header_encoding'];
// Пробелы в конце оставляем незакодированными.
preg_match('/^(.*?)(\s*)$/s', $p[1], $sp);
return "=?$encoding?B?".base64_encode($sp[1])."?=".$sp[2];
}
lib/socketmail.php
// Функция для посылки почты вручную, минуя SMTP-сервер провайдера.
// Использует getmxrr(), которая в Windows не поддерживается.
function mailx_manual($mail, &$reason=false) {
$reason = [];
// Выделяем заголовок To.
$to = "";
if (preg_match('/^To:\s*([^\r\n]*)[\r\n]*/m', $mail, $p)) {
$to = @$p[1]; // сохраняем
}
// Вначале выделяем имя хоста.
if (!preg_match('/@([\w.-]*)/', $to, $pockets)) {
$reason[] = "invalid e-mail address - $to: no host";
return false;
}
$host = $pockets[1];
// По стандарту SMTP, если нет ни одной MX-записи, то в качестве
// почтового сервера выступает сам хост.
if (!getmxrr($host, $mxes, &$weights)) {
$mxes = array($host);
}
// Проходимся по всем MX-записям. Для простоты веса не
// учитываем - большой беды от этого не будет.
foreach ($mxes as $mx) {
$result = socketmail($mx, $mail, $r);
$reason = array_merge($reason, $r);
// Если письмо отослано, все сделано.
if ($result) return true;
}
$reason[] = "could not connect to any of (".join(", ", $mxes).")";
return false;
}
// Функция открывает соединение с SMTP-сервером $host
// и пытается отправить через него письма, заданные в $mails.
// Все ошибки накапливаются в необязательной переменной $reason
// в виде списка строк. В случае, если отправка всех писем
// прошла успешно, функция возвращает true, иначе - false.
// Каждое письмо в $mails должно быть представлено в формате:
// "Заголовки\r\n\r\nТело". Функция не заботится о правильном
// кодировании заголовков, предполагая, что это уже было
// сделано ранее.
function socketmail($host, $mails, &$reason=false) {
// Открываем соединение с почтовым сервером.
$reason = [];
$f = @fsockopen($host, 25, $errno, $errstr, 30);
if (!$f) {
$reason[] = "could not open $host:25";
return false;
}
$answer = fgets($f, 1024);
// Сигнализируем о начале обмена данными.
fputs($f, "HELO {$_SERVER['SERVER_NAME']}\r\n");
$answer = fgets($f, 1024);
// Отправляем все письма для этого сервера в цикле.
// Все это происходит за одно соединение с сервером.
if (!is_array($mails)) $mails = array($mails);
foreach ($mails as $i=>$data) {
// Получаем заголовки и тело сообщения.
list ($headers, $body) = preg_split('/\r?\n\r?\n/', $data, 2);
// Получаем заголовок From.
if (!preg_match('/^From:\s*(.*)/mi', $headers, $pockets)) {
$reason[] = "could not find required From: header in mail #$i";
continue;
}
$from = getEmail($pockets[1]);
if (!$from) {
$reason[] = "no email in From: header in mail #$i";
continue;
}
// Получаем заголовок To.
if (!preg_match('/^To:\s*(.*)/mi', $headers, $pockets)) {
$reason[] = "could not find required To: header in mail #$i";
continue;
}
$to = getEmail($pockets[1]);
if (!$to) {
$reason[] = "no email in To: header in mail #$i";
continue;
}
// Т.к. точка в протоколе SMTP свидетельствует о конце данных,
// (см. ниже), мы ее удваиваем.
$data = preg_replace("/\n\./", "\n..", $data);
// Отправляем управляющие команды SMTP.
do {
if (!smtp_say($f, "MAIL FROM: <$from>", $reason)) break;
if (!smtp_say($f, "RCPT TO: <$to>", $reason)) break;
if (!smtp_say($f, "DATA", $reason)) break;
// Печатаем данные.
fputs($f, trim($data)."\r\n");
if (!smtp_say($f, ".", $reason)) break;
} while (false);
// Конец письма.
!smtp_say($f, "RSET", $reason);
}
// Говорим серверу об окончании работы.
@smtp_say($f, "QUIT", $reason);
fclose($f);
return !count($reason);
}
function smtp_say($f, $cmd, &$reason) {
fputs($f, "$cmd\r\n");
$answer = fgets($f, 1024);
# echo "> $cmd<br>< $answer<br>";
if (!preg_match('/^(250|354|221)/', $answer)) { $reason[] = "$answer"; return; }
return true;
}
// Извлекает первый адрес E-mail из заголовка To или From.
// Внимание: упрощенная версия!
function getEmail($header) {
if (!preg_match('/([\w.-]+@[\w.-]+)/s', $header, $p)) return;
return $p[1];
}
mail.php.eml - активный шаблон письма
From: Почтовый робот <somebody@mail.ru>
To: <?=$to?>
Subject: Добрый день!
Content-type: text/plain; charset=windows-1251
Привет, <?=$to?>!
<?=$text?>
Содержимое переменных окружения на момент отправки письма:
<?print_r($_SERVER)?>
Это сообщение сгенерировано роботом - не отвечайте на него.
Здесь представлен полностью работающий скрипт, Вы можете собрать его сами в одну папку, поправить адреса и запустить.
Всего за 1500 рублей (~20$) Вы можете приобрести готовый rar-архив, содержащий скрипт, который также поддерживает весь функционал, и имеет дополнительные отладочные опции. Код скрипта реализован на PHP, полностью открытый и не использует никаких дополнительных библиотек.
Соглашение по использованию платной версии:
- Вы можете использовать полученный код в любых своих разработках, вы не обязаны указывать ссылку на источник.
- Вы НЕ имеете права перепродавать её, размещать в свободном или ограниченном доступе, а также публиковать в любом виде.
- Все остальные права сохраняются за автором.
Регистрация Войти Войти через VK Войти через Яндекс
.
Прокомментировать/Отблагодарить