Каков правильный способ ОТЧЕТНОЙ почты от кода (Python)?

Отказ от ответственности: я колебался в названии, из-за широкого характера этого вопроса (см. Ниже ;-), другие варианты включены:

  • Как отправить письмо с localhost, используя только код Python?
  • Как отправить письмо из кода Python, без использования внешнего SMTP-сервера?
  • Можно ли отправить электронное письмо ПРЯМО в это место назначения, используя только localhost и Python?

Сначала немного контекста:
Ради обучения я создаю сайт с функцией регистрации пользователей. Идея заключается в том, что после регистрации пользователь получит письмо со ссылкой для активации. Я хотел бы составить и отправить электронное письмо из кода Python, и это та часть, где я хотел бы попросить некоторые разъяснения.


Мое понимание до того, как я начал (очевидно, наивный =), можно проиллюстрировать следующим образом (учитывая, что существует законный адрес электронной почты [email protected]):

My naive understanding

После поиска примеров я наткнулся на некоторые вопросы и ответы по stackoverflow (1, 2, 3, 4). Из этого я извлек следующий фрагмент кода, чтобы составить и отправить электронное письмо из кода Python:

import smtplib

from email.message import EmailMessage

message = EmailMessage()
message.set_content('Message content here')
message['Subject'] = 'Your subject here'
message['From'] = '[email protected]'
message['To'] = '[email protected]'

smtp_server = smtplib.SMTP('smtp.server.address:587')
smtp_server.send_message(message)
smtp_server.quit()

Следующий (очевидный) вопрос заключался в том, что передать smtplib.SMTP() вместо 'smtp.server.address:587'. Из комментариев к этому ответу я обнаружил, что локальный SMTP-сервер (хотя и для целей тестирования) можно запустить через python3 -m smtpd -c DebuggingServer -n localhost:1025, затем smtp_server = smtplib.SMTP('smtp.server.address:587') может быть изменен на smtp_server = smtplib.SMTP('localhost:1025') и все отправленные электронные письма будут отображаться в консоли (откуда python3 -m smtpd -c DebuggingServer -n localhost:1025 Команда python3 -m smtpd -c DebuggingServer -n localhost:1025 была выполнена), этого было достаточно для тестирования - это было не то, что я хотел (моя цель была - возможность отправлять почту на "реальный" адрес электронной почты с локальной машины, используя только код Python).

Итак, следующим шагом будет настройка локального SMTP-сервера, способного отправлять электронную почту на внешний "реальный" адрес электронной почты (как я хотел сделать все это из кода Python, так что сам сервер лучше реализовать в Питон тоже). Я вспомнил, как читал в каком-то журнале (в начале 2000 года), что спамеры используют локальные серверы для отправки почты (эта конкретная статья говорила о Sambar, разработка которого закончилась в 2007 году, и которая не была написана на Python :-) Я думал там должно быть какое-то современное решение с аналогичной функциональностью. Поэтому я начал искать, я надеялся найти (в стеке или в другом месте) достаточно короткий фрагмент кода, который будет делать то, что я хотел. Я не нашел такого фрагмента кода, но натолкнулся на фрагмент под названием (Python) "Отправить электронную почту без почтового сервера" (который использует API-интерфейс chilkat), хотя все, что мне было нужно (предположительно), было прямо там, в комментариях к коду, В первой строке четко указано:

Реально ли отправлять электронную почту без подключения к почтовому серверу? На самом деле, нет.

и несколько строк ниже:

Вот что происходит внутри тех других компонентов, которые утверждают, что им не нужен почтовый сервер: Компонент выполняет поиск DNS MX, используя адрес электронной почты получателя, чтобы найти почтовый сервер (т.е. SMTP-сервер) для этого домена. Затем он подключается к этому серверу и доставляет электронную почту. Вы все еще подключаетесь к SMTP-серверу, но не к ВАШЕМУ серверу.

Прочитав это, я понял - мне явно не хватало некоторых деталей в моем понимании (как показано на картинке выше) процесса. Чтобы исправить это, я прочитал весь RFC на SMTP.


После прочтения RFC моё лучшее понимание процесса может быть изображено так: My improved understanding


Из этого понимания пришли реальные вопросы, которые я хотел бы уточнить:

  1. Можно ли считать мое "улучшенное понимание" правильным, как общую картину?
  2. Какие именно адреса возвращаются поиском MX?

    • используя host -t mx gmail.com (предложенную этим ответом), я смог получить следующее:

      gmail.com mail is handled by 10 alt1.gmail-smtp-in.l.google.com.
      gmail.com mail is handled by 20 alt2.gmail-smtp-in.l.google.com.
      gmail.com mail is handled by 40 alt4.gmail-smtp-in.l.google.com.
      gmail.com mail is handled by 30 alt3.gmail-smtp-in.l.google.com.
      gmail.com mail is handled by 5 gmail-smtp-in.l.google.com.
      
    • но ничего из этого не упоминается в официальных документах (см. smtp-relay.gmail.com, smtp.gmail.com, aspmx.l.google.com)
  3. Всегда ли требуется аутентификация для передачи электронной почты на SMTP-сервер установленного почтового сервиса (скажем, gmail)?

    • Я понимаю, что для использования, скажем, smtp.gmail.com для отправки почты вам понадобится, независимо от того, есть у @gmail адрес @gmail или нет (как указано в документации):

      Ваш полный адрес электронной почты Gmail или G Suite необходим для аутентификации.

    • Но если электронное письмо на адрес [email protected] будет отправлено на SMTP-сервер, не принадлежащий gmail, оно будет перенаправлено на один из серверов gmail (напрямую или через шлюз/ретранслятор). В этом случае (я полагаю) отправителю электронной почты нужно будет пройти аутентификацию только при отправке почты, поэтому после этого сервер gmail будет принимать почту без аутентификации?

      • Если да, что мешает мне "притворяться" таким шлюзом/ретранслятором и передавать сообщения электронной почты непосредственно назначенным SMTP? Тогда также должно быть довольно легко написать "прокси-SMTP", который будет просто искать соответствующий сервер через поиск MX и перенаправлять ему электронные письма, вроде как напрямую.
  4. В документации по SMTP gmail также упоминается сервер aspmx.l.google.com, который не требует аутентификации, хотя:

    Почту можно отправлять только пользователям Gmail или G Suite.

    Учитывая это, я предполагаю, что следующий фрагмент должен работать для отправки почты на почтовый ящик [email protected]:

    import smtplib
    
    from email.message import EmailMessage
    
    message = EmailMessage()
    message.set_content('Message test content')
    message['Subject'] = 'Test mail!'
    message['From'] = '[email protected]'
    message['To'] = '[email protected]'
    
    smtp_server = smtplib.SMTP('aspmx.l.google.com:25')
    smtp_server.send_message(message)
    smtp_server.quit()
    

    При OSError: [Errno 65] No route to host приведенный выше код (с заменой [email protected] действительной почтой) выдает OSError: [Errno 65] No route to host. Все, что я хочу подтвердить, это то, что сообщение aspmx.l.google.com правильно обрабатывается в коде.

Ответ 1

Ваше понимание того, как работает почта, является примерно правильным. Некоторые дополнительные заметки, которые могут прояснить ситуацию:

  • SMTP используется для двух различных целей. Кажется, вы смешиваете эти два:

    • Первое использование, обычно называемое "отправкой", заключается в отправке почты из MUA (агента почтового пользователя, вашей почтовой программы, Outlook, Thunderbird,...) в MTA (агент передачи почты, обычно называемый "почтовый сервер"), MTA управляются вашим интернет-провайдером или почтовыми провайдерами, такими как GMail. Как правило, их использование ограничено либо IP-адресом (только клиенты указанного интернет-провайдера могут его использовать), либо имя пользователя/пароль.

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

Чтобы отправить почту, вам потребуется, по крайней мере, вторая часть SMTP: возможность разговаривать с другим MTA для доставки почты.

Типичным способом отправки писем является составление почты в вашем приложении, а затем отправьте ее на почтовый сервер MTA для доставки. В зависимости от вашей установки этот MTA может быть установлен на том же компьютере, на котором запущен ваш код Python (localhost), или может быть более "центральным" почтовым сервером (возможно, требующим аутентификации).

"Ваш" MTA позаботится обо всех неприятных деталях доставки почты, таких как:

  • Выполнение DNS-поиска, чтобы узнать, как MTA связывается для ретрансляции почты. Это включает в себя MX-поиск, но также и другие резервные механизмы, такие как A-записи.

  • Повторная доставка, если первая попытка временно не удалась

  • Генерирование сообщения об отказе, если сообщение не выполняется постоянно

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

  • Подписание сообщения с помощью DKIM, чтобы уменьшить вероятность того, что он будет помечен как СПАМ.

  • ...

Разумеется, вы могли бы повторно реализовать все эти функции в своем собственном коде Python и эффективно объединить MTA с вашим приложением, но я категорически не соглашаюсь с этим. Почта на удивление трудно получить право...

Итог: попробуйте отправить почту через SMTP на почтовый сервер вашего провайдера или другую почтовую службу. Если это невозможно: подумайте очень сложно, если вы хотите запустить свой собственный почтовый сервер. Быть отмеченным как спамер происходит легко; удаление из спам-листов намного сложнее. Не переустанавливайте SMTP-код в своем приложении.

Ответ 2

Благодаря этим ответам, на мои дополнительные вопросы: 1, 2, 3, а также на эти два вопроса (и ответы) других людей: один, два - я думаю, что теперь я готов ответить на вопросы, которые я разместил на своем своя.


Я отвечу на вопросы один за другим:

  1. Да, как общая картина, отправка электронного письма может быть изображена так: My improved understanding

  2. MX lookup возвращает адрес (а) серверов, которые получают электронную почту, предназначенную для указанного домена.

    • Что касается "Почему smtp-relay.gmail.com, smtp.gmail.com, aspmx.l.google.com не возвращаются командой host -t mx gmail.com?". Этот момент в значительной степени рассматривается в другом ответе на этот вопрос. Основные моменты, на которые следует обратить внимание:
      • серверы, возвращаемые поиском MX, отвечают за получение сообщений электронной почты для домена (в данном случае gmail)
      • серверы, перечисленные в документах gmail, предназначены для отправки почты (т.е. сообщения, которые пользователь gmail хочет отправить другому пользователю gmail или иным образом, передаются на эти серверы)
  3. Аутентификация не требуется для серверов, получающих электронные письма (т.е. Те, которые возвращены поиском MX).

    • Есть пара вещей, которые предотвращают злоупотребление такими серверами:
      • многие интернет-провайдеры блокируют исходящие подключения к порту 25 (который является портом по умолчанию для серверов приема почты), чтобы предотвратить такую "прямую" отправку почты
      • Есть множество мер, принятых на стороне принимающих серверов, которые, в основном, предназначены для предотвращения рассылки спама, но в результате, вероятно, также предотвратят такую "прямую" отправку почты (некоторые примеры: DNSBL - список заблокированных IP-адреса, DKIM - это метод проверки подлинности электронной почты, предназначенный для обнаружения поддельных адресов отправителей в электронных письмах (если у вас нет своего собственного, законного почтового сервера, вы будете использовать чужой домен для поля " From, где DKIM может ударить вас )
  4. Фрагмент кода в порядке. Ошибка возникает, по всей вероятности, из-за блокировки на стороне провайдера.


С учетом всего сказанного, фрагмент кода:

import smtplib

from email.message import EmailMessage

message = EmailMessage()
message.set_content('Message content here')
message['Subject'] = 'Your subject here'
message['From'] = '[email protected]'
message['To'] = '[email protected]'

smtp_server = smtplib.SMTP('smtp.server.address:25')
smtp_server.send_message(message)
smtp_server.quit()

фактически отправит электронное письмо (см. этот вопрос, для реального рабочего примера), учитывая, что smtp.server.address:25 является законным сервером и нет блокировок на стороне провайдера и/или smtp.server.address.