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

Статьи

Отправка писем через SMTP с авторизацией

В этой статье я расскажу, как отправлять почту через SMTP с авторизацией помощью PHP. Рассматривать будем вариант отправки именно с авторизацией, так как SMTP без авторизации в данный момент почти не существует. Для отправки письма через SMTP потребуется возможность работы php с сокетами. На бесплатных хостингах использование сокетов может быть запрещено.

Для чего же может потребоваться отправка почты из php через smtp, ведь в php есть готовая функция отправки почты mail()?

  1. Основная причина, это проблема фильтров антиспама. Современные сервера настроены таким образом, что отправляя письмо через функцию mail(), в заголовки письма добавляется информация, по которой очевидно, что почта была отправлена с помощью mail(). Вот этого и не любят фильтры антиспама на стороне получателя. Не все конечно, но если фильтр настроен жестко, то скорее всего такое письмо будет удалено фильтром на стороне получателя. Можно конечно избавиться от таких заголовков с помощью настройки сервера(если у вас есть доступ к настройке сервера), но это уже вариант сложный и не всем подходит.
  2. Специфическая причина. Можно полностью замаскировать письмо, как будто оно было отправленно почтовой программой.

Отправка почты вручную

Сначала немного разберем протокол SMTP, и попробуем отправить почту с помощью командной строки винды, чтобы понять принцип общения с smtp сервером. Тестировать вам придется на своем smtp сервере, надеюсь у каждого есть почта на бесплатных серверах, вот на них и можете потестировать. Для примера буду использовать mail.ru.

Запускаем командную строку (Пуск/Выполнить/cmd). В открывшемся окне пишем

telnet smtp.mail.ru 25

Если соединение прошло нормально, сервер должен ответить примерно так

220 mail.ru ESMTP Sat, 11 Aug 2007 17:32:14 +0400

Теперь здороваемся с сервером

EHLO mail.ru

Если все ОК, ответ будет

250-mx30.mail.ru Hello mail.ru [80.64.80.192]
250-SIZE 10485760
250-8BITMIME
250-AUTH PLAIN LOGIN
250 PIPELINING

После подобного ответа можно вводить логин и пароль для авторизации. Для этого вводим команду

AUTH LOGIN

получаем ответ

334 VXNlcm5hbWU6

И вот сейчас надо вводить пароль логин, но они должны быть закодированы. Самый простой способ закодировать логин и пароль, это при помощи функции php base64_encode(). Создайте следующий php код и запустите его на тестовом сервере.

<?php
print base64_encode("логин");
print 
'<br/>';
print 
base64_encode("пароль");
?>

Коды логина и пароля получены, теперь можно ввести их. Копируйте и вставляйте их по очереди. После ввода логина должно появиться сообщение с кодом 334. После ввода правильного пароля должно появиться

235 Authentication succeeded

Авторизация пройдена. Сейчас указываем от кого будет написано письмо. Указываете свой ящик, от имени которого авторизовались.

MAIL FROM:login@mail.ru

Если сервер принял этот адрес, получите ответ

250 OK

Теперь указываем email получателя

RCPT TO:asd@qwe.ru

положительный ответ сервера

250 Accepted

Если нужно письмо отправить нескольким адресатам, повторяем команду RCPT TO: сколько нужно раз. Будет разослано одно письмо многим адресатам. А сейчас настало время самого письма. Вводим команду

DATA

Ответ будет примерно таким

354 Enter message, ending with "." on a line by itself

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

Subject: Это тема письма
To: asd@qwe.ru
X-Mailer: webi.ru mailer

Отделили заголовки пустой строкой, и теперь пишем текст письма. А чтобы закончить ввод письма, нужно на отдельной строке ввести точку.

.

Когда введете точку, получите такой ответ

250 OK id=1IiR72-000ONs-00

Теперь завершаем работу с сервером.

QUIT

ответ

221 mx30.mail.ru closing connection

Вот такой принцип общения с SMTP сервером. Здесь я показал только принцип. Полное описание протокола smtp, все команды и коды ответов сервера читайте в документах RFC821, RFC2554.

Отправка через PHP

А сейчас все это общение с сервером переведем на php. После каждой передачи команды на сервер, нужно будет получать ответ от сервера. Для этого сделаем маленькую функцию, эта функция будет работать с открытым соединением и получать ответ от сервера.

<?php
function get_data($smtp_conn)
{
  
$data="";
  while(
$str fgets($smtp_conn,515))
  {
    
$data .= $str;
    if(
substr($str,3,1) == " ") { break; }
  }
  return 
$data;
}
?>

Дальше, сразу создадим необходимые заголовки. Привожу пример классических заголовков, на примере почтовика The bat. Это письмо не будет отличаться от отправленного через этот почтовик.

<?php
$header
="Date: ".date("D, j M Y G:i:s")." +0700\r\n";
$header.="From: =?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode('Максим')))."?= <login@mail.ru>\r\n";
$header.="X-Mailer: The Bat! (v3.99.3) Professional\r\n";
$header.="Reply-To: =?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode('Максим')))."?= <login@mail.ru>\r\n";
$header.="X-Priority: 3 (Normal)\r\n";
$header.="Message-ID: <172562218.".date("YmjHis")."@mail.ru>\r\n";
$header.="To: =?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode('Сергей')))."?= <asd@qwe.ru>\r\n";
$header.="Subject: =?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode('проверка')))."?=\r\n";
$header.="MIME-Version: 1.0\r\n";
$header.="Content-Type: text/plain; charset=windows-1251\r\n";
$header.="Content-Transfer-Encoding: 8bit\r\n";
?>

Переменную с заголовками создали, теперь создадим переменную с текстом самого письма...

$text="привет, проверка связи.";

А сейчас открываем соединение с smtp сервером

$smtp_conn fsockopen("smtp.mail.ru"25$errno$errstr10);

После открытия соединения читаем ответ от сервера в переменную $data

$data get_data($smtp_conn);

Ну а теперь начинаем запускать все те команды, которые вводили руками до этого. И после каждого ввода команды считываем ответ от сервера...

<?php
fputs
($smtp_conn,"EHLO mail.ru\r\n");
$data get_data($smtp_conn);

fputs($smtp_conn,"AUTH LOGIN\r\n");
$data get_data($smtp_conn);

fputs($smtp_conn,base64_encode("login")."\r\n");
$data get_data($smtp_conn);

fputs($smtp_conn,base64_encode("password")."\r\n");
$data get_data($smtp_conn);

fputs($smtp_conn,"MAIL FROM:login@mail.ru\r\n");
$data get_data($smtp_conn);

fputs($smtp_conn,"RCPT TO:asd@qwe.ru\r\n");
$data get_data($smtp_conn);

fputs($smtp_conn,"DATA\r\n");
$data get_data($smtp_conn);

fputs($smtp_conn,$header."\r\n".$text."\r\n.\r\n");
$data get_data($smtp_conn);

fputs($smtp_conn,"QUIT\r\n");
$data get_data($smtp_conn);
?>

Вот и улетело письмишко.

Для чего же читать ответ от сервера после каждой команды? Как видно, после каждой команды сервер отвечает кодом из трех цифр и текстовым пояснением. И после каждого ввода команды нужно знать, принял сервер команду или нет. Выдернуть код очень просто, надо просто получить первые три символа строки

$code substr($data03);

Какие коды должен выдавать сервер и на какие команды, читайте в спецификации протокола smtp.

Отправка нескольких писем за одно соединение

Если вы хотите отправить одно письмо многим адресатам, то вам достаточно использовать команду RCPT TO несколько раз подряд, указывая в ней email адреса, на которые хотите разослать письмо. После этого как и положено идет DATA и дальше письмо. И ваше письмо разошлется по всем указанным адресам.

Если хотите отправить несколько разных писем, разным адресатам, тогда делаем так: сначала идет все стандартно, соеденились, авторизовались, отправили письмо и после отправки письма посылаем команду не выход, а сброс RSET. Эта команда сбрасывает все, что было введенно в текущем сеансе и после этого можно начинать формировать и отправлять следующее письмо. Начинаем отправлять снова

MAIL FROM:.......
RCPT TO:......

и т.д. То есть формируем с самого начала новое письмо. После завершения отправки можно снова ввести RSET и сформировать третье письмо, четвертое и т.д. Ну и в самом конце уже ставим QUIT.

Немного о заголовках

  • Date: — дата написания (отправки) письма
  • X-Mailer: — почтовая программа, которая отправляла письмо
  • X-Priority: — приоритет
  • From: — от кого
  • Reply-To: — куда писать ответ, при нажатии на кнопку ОТВЕТИТЬ
  • To: — кому
  • Subject: — тема

В полях From, Reply-To, To можно указывать не только email, но и имя (Максим ). Вот только при написании имен русскими символами, придется их конвертировать

=?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode('Максим')))."?= <login@mail.ru>

По такому же принципу кодируется и тема.

Еще одно интересное поле Message-ID: Это идентификатор сообщения. Должен быть уникальным в пределах вселенной. В примере я использовал принцип создания идентификатора почтовика The bat.

Message-ID: <172562218.".date("YmjHis")."@mail.ru>

после преобразования получится примерно так

Message-ID: <172562218.2007081310152@mail.ru>

Каждые почтовик создают это поле по-своему. The bat создает именно так. Первая часть до точки это должно быть случайным числом, вторая часть после точки это дата без разделителей. Поле Message-ID не является обязательным, а в некоторых случаях сервер сам подставляет это поле. Но лучше его использовать, так как некоторые фильтры антиспама проверяют это поле. И если вы указали в заголовках, что письмо было написано якобы через The bat, то и Message-ID должен быть сгенерирован по правилам этого почтовика.

Можете конечно использовать свое имя почтовика, например

X-Mailer: webi.ru (v2.32.3)

но сильно тупые фильтры антиспама будут принимать вашу почту за спам.

Прикрепление файлов к письму

На предыдущем примере вложим два файла. Для этого нужно внести поправку в заголовки. Заменяем вот эти два заголовка

Content-Type: text/plain; charset=windows-1251
Content-Transfer-Encoding: 8bit

на

Content-Type: multipart/mixed; boundary="----------A4D921C2D10D7DB"

Этот заголовок означает, что в письме будут разные типы данных, и разделитель между этими данными будет "----------A4D921C2D10D7DB".

Разделитель может быть любым, на ваше усмотрение, главное правило, чтобы разделитель (метка) начинался с "--" и чтобы такая последовательность символов не встречалась в тексте письма.

Файлы будут вставляться в тело письмо в перекодированном виде в base64. Подготовим две текстовые переменные, содержащие файлы в кодированном виде.

<?php
$file
="path/1.jpg";
$fp fopen($file"rb");
$code_file1 chunk_splitbase64_encodefread$fpfilesize($file) ) ) );
fclose$fp );
?>

Обратите внимание, как читается и кодируется файл. Вместе с кодированием он разбивается на строки по 76 символов, чтобы не было длинной строки, так как длина строки в почтовом формате ограничена. Таким образом, можно вставить не только jpg, но и любой бинарный файл.

Итак, один файл готов к вложению и сейчас создадим еще один файл, текстовый. Текстовый файл необязательно читать с диска, можно создать его сразу из текста.

$code_file2 base64_encode("привет, это типа второй файл");

Вот таким образом создали вторую текстовую переменную, которая содержит текстовые данные в base64. И теперь можно редактировать переменную $text, которая у нас является телом письма.

<?php
$text 
"------------A4D921C2D10D7DB
Content-Type: text/plain; charset=windows-1251
Content-Transfer-Encoding: 8bit

привет, это текст письма

------------A4D921C2D10D7DB
Content-Type: application/octet-stream; name=\"1.jpg\"
Content-transfer-encoding: base64
Content-Disposition: attachment; filename=\"1.jpg\"

"
.$code_file1."
------------A4D921C2D10D7DB
Content-Type: application/octet-stream; name=\"2.txt\"
Content-transfer-encoding: base64
Content-Disposition: attachment; filename=\"2.txt\"

"
.$code_file2."
------------A4D921C2D10D7DB--
"
;
?>

Сначала ставим разделитель, указывая, что сейчас пойдет первая часть письма и после разделителя указываем какой тип данных будет использован.

Content-Type: text/plain; charset=windows-1251
Content-Transfer-Encoding: 8bit

Здесь указано, что сейчас пойдет обычный текст. Обязательно заголовки нужно отделить пустой строкой, пустая строка признак того, что заголовок кончился и пошло тело.

Когда надо начать вставлять вложения, нужно поставить разделитель(метку), это будет признаком, что одна часть кончилась и начинается следующая.

------------A4D921C2D10D7DB
Content-Type: application/octet-stream; name="1.jpg"
Content-transfer-encoding: base64
Content-Disposition: attachment; filename="1.jpg"

Вот поставлен разделитель, значит началась вторая часть. Так же надо указать какие данные будут в этой части. Данные заголовки показывают, что сейчас пойдет кодированный в base64 файл с именем 1.jpg. После заголовков ставим пустую строку и вставляем кодированный текст $code_file1. Потом опять ставим разделитель и снова указываем заголовки, что пойдет еще один файл. И т.д. Так можно вкладывать файлы еще и еще.

Когда вложения закончены, нужно завершить это тем же самым разделителем, только в конце должно быть "--", это будет признаком окончания вложений.

Ну а дальше, как обычно, вставляем это все в отправку на сервер

fputs$smtp_conn,$header."\r\n".$text."\r\n.\r\n" );

Еще раз уточню, разделитель "------------A4D921C2D10D7DB" я создал случайным образом, он может быть любой по вашему усмотрению, главное, он должен начинаться с "--" и не встречаться в самом письме.

А сейчас полные примеры без комментариев...

Отправка письма без вложений

<?php
function get_data($smtp_conn)
{
  
$data="";
  while(
$str fgets($smtp_conn,515))
  {
    
$data .= $str;
    if(
substr($str31) == " ") break;
  }
  return 
$data;
}

$header="Date: ".date("D, j M Y G:i:s")." +0700\r\n";
$header.="From: =?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode('Максим')))."?= <login@mail.ru>\r\n";
$header.="X-Mailer: The Bat! (v3.99.3) Professional\r\n";
$header.="Reply-To: =?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode('Максим')))."?= <login@mail.ru>\r\n";
$header.="X-Priority: 3 (Normal)\r\n";
$header.="Message-ID: <172562218.".date("YmjHis")."@mail.ru>\r\n";
$header.="To: =?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode('Сергей')))."?= <qwe@asd.ru>\r\n";
$header.="Subject: =?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode('проверка')))."?=\r\n";
$header.="MIME-Version: 1.0\r\n";
$header.="Content-Type: text/plain; charset=windows-1251\r\n";
$header.="Content-Transfer-Encoding: 8bit\r\n";

$text="привет, проверка связи.";

$smtp_conn fsockopen("smtp.mail.ru"25,$errno$errstr10);
if(!
$smtp_conn) {print "соединение с серверов не прошло"fclose($smtp_conn); exit;}
$data get_data($smtp_conn);
fputs($smtp_conn,"EHLO mail.ru\r\n");
$code substr(get_data($smtp_conn),0,3);
if(
$code != 250) {print "ошибка приветсвия EHLO"fclose($smtp_conn); exit;}
fputs($smtp_conn,"AUTH LOGIN\r\n");
$code substr(get_data($smtp_conn),0,3);
if(
$code != 334) {print "сервер не разрешил начать авторизацию"fclose($smtp_conn); exit;}

fputs($smtp_conn,base64_encode("login")."\r\n");
$code substr(get_data($smtp_conn),0,3);
if(
$code != 334) {print "ошибка доступа к такому юзеру"fclose($smtp_conn); exit;}

fputs($smtp_conn,base64_encode("password")."\r\n");
$code substr(get_data($smtp_conn),0,3);
if(
$code != 235) {print "не правильный пароль"fclose($smtp_conn); exit;}

fputs($smtp_conn,"MAIL FROM:login@mail.ru\r\n");
$code substr(get_data($smtp_conn),0,3);
if(
$code != 250) {print "сервер отказал в команде MAIL FROM"fclose($smtp_conn); exit;}

fputs($smtp_conn,"RCPT TO:qwe@asd.ru\r\n");
$code substr(get_data($smtp_conn),0,3);
if(
$code != 250 AND $code != 251) {print "Сервер не принял команду RCPT TO"fclose($smtp_conn); exit;}

fputs($smtp_conn,"DATA\r\n");
$code substr(get_data($smtp_conn),0,3);
if(
$code != 354) {print "сервер не принял DATA"fclose($smtp_conn); exit;}

fputs($smtp_conn,$header."\r\n".$text."\r\n.\r\n");
$code substr(get_data($smtp_conn),0,3);
if(
$code != 250) {print "ошибка отправки письма"fclose($smtp_conn); exit;}

fputs($smtp_conn,"QUIT\r\n");
fclose($smtp_conn);
?>

Отправка письма с вложениями

<?php
function get_data($smtp_conn)
{
  
$data="";
  while(
$str fgets($smtp_conn,515))
  {
    
$data .= $str;
    if(
substr($str31) == " ") { break; }
  }
  return 
$data;
}

$header="Date: ".date("D, j M Y G:i:s")." +0700\r\n";
$header.="From: =?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode('Максим')))."?= <login@mail.ru>\r\n";
$header.="X-Mailer: The Bat! (v3.99.3) Professional\r\n";
$header.="Reply-To: =?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode('Максим')))."?= <login@mail.ru>\r\n";
$header.="X-Priority: 3 (Normal)\r\n";
$header.="Message-ID: <172562218.".date("YmjHis")."@mail.ru>\r\n";
$header.="To: =?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode('Сергей')))."?= <qwe@asd.ru>\r\n";
$header.="Subject: =?windows-1251?Q?".str_replace("+","_",str_replace("%","=",urlencode('проверка')))."?=\r\n";
$header.="MIME-Version: 1.0\r\n";
$header.="Content-Type: multipart/mixed; boundary=\"----------A4D921C2D10D7DB\"\r\n";

$file="path/1.jpg";
$fp fopen($file"rb");
$code_file1 chunk_split(base64_encode(fread($fpfilesize($file))));
fclose($fp);
$code_file2=base64_encode("привет, это типа второй файл");

$text="------------A4D921C2D10D7DB
Content-Type: text/plain; charset=windows-1251
Content-Transfer-Encoding: 8bit

привет, это текст письма

------------A4D921C2D10D7DB
Content-Type: application/octet-stream; name=\"1.jpg\"
Content-transfer-encoding: base64
Content-Disposition: attachment; filename=\"1.jpg\"

"
.$code_file1."
------------A4D921C2D10D7DB
Content-Type: application/octet-stream; name=\"2.txt\"
Content-transfer-encoding: base64
Content-Disposition: attachment; filename=\"2.txt\"

"
.$code_file2."
------------A4D921C2D10D7DB--
"
;

$smtp_conn fsockopen("smtp.mail.ru"25,$errno$errstr10);
if(!
$smtp_conn) {print "соединение с серверов не прошло"fclose($smtp_conn); exit;}
$data get_data($smtp_conn);
fputs($smtp_conn,"EHLO mail.ru\r\n");
$code substr(get_data($smtp_conn),0,3);
if(
$code != 250) {print "ошибка приветсвия EHLO"fclose($smtp_conn); exit;}
fputs($smtp_conn,"AUTH LOGIN\r\n");
$code substr(get_data($smtp_conn),0,3);
if(
$code != 334) {print "сервер не разрешил начать авторизацию"fclose($smtp_conn); exit;}

fputs($smtp_conn,base64_encode("login")."\r\n");
$code substr(get_data($smtp_conn),0,3);
if(
$code != 334) {print "ошибка доступа к такому юзеру"fclose($smtp_conn); exit;}

fputs($smtp_conn,base64_encode("password")."\r\n");
$code substr(get_data($smtp_conn),0,3);
if(
$code != 235) {print "не правильный пароль"fclose($smtp_conn); exit;}

fputs($smtp_conn,"MAIL FROM:login@mail.ru\r\n");
$code substr(get_data($smtp_conn),0,3);
if(
$code != 250) {print "сервер отказал в команде MAIL FROM"fclose($smtp_conn); exit;}

fputs($smtp_conn,"RCPT TO:qwe@asd.ru\r\n");
$code substr(get_data($smtp_conn),0,3);
if(
$code != 250 AND $code != 251) {print "Сервер не принял команду RCPT TO"fclose($smtp_conn); exit;}

fputs($smtp_conn,"DATA\r\n");
$code substr(get_data($smtp_conn),0,3);
if(
$code != 354) {print "сервер не принял DATA"fclose($smtp_conn); exit;}

fputs($smtp_conn,$header."\r\n".$text."\r\n.\r\n");
$code = <;/font>substr(get_data($smtp_conn),0,3);
if(
$code != 250) {print "ошибка отправки письма"fclose($smtp_conn); exit;}

fputs($smtp_conn,"QUIT\r\n");
fclose($smtp_conn);
?>

Источник: http://webi.ru