Отправка почты по шаблону минуя 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)?>
Это сообщение сгенерировано роботом - не отвечайте на него.

Здесь представлен полностью работающий скрипт, Вы можете собрать его сами в одну папку, поправить адреса и запустить.

Всего за 990 рублей (~11$) Вы можете приобрести готовый rar-архив, содержащий скрипт, который также поддерживает весь функционал, и имеет дополнительные отладочные опции. Код скрипта реализован на PHP, полностью открытый и не использует никаких дополнительных библиотек.

Соглашение по использованию платной версии:

  • Вы можете использовать полученный код в любых своих разработках, вы не обязаны указывать ссылку на источник.
  • Вы НЕ имеете права перепродавать её, размещать в свободном или ограниченном доступе, а также публиковать в любом виде.
  • Все остальные права сохраняются за автором.

Регистрация Войти Войти через VK Войти через FB Войти через Google Войти через Яндекс

При нажатии кнопки Оплатить и загрузить, Вы подтверждаете согласие с условиями использования скрипта, описанными на этой странице.
Вы будете перенаправлены на страницу выбора способа оплаты, после оплаты 990 рублей (~11$) начнется загрузка файла.
Чтобы мы не потеряли Вашу оплату при потере соединения, укажите Ваш действующий
адрес электронной почты
Сомневаетесь? Вы всегда сможете задать вопросы и получить помощь.


.