SMTP dla Pythona — poczta ze skryptu albo aplikacji Django, która dociera
Python ma SMTP wbudowane: dzięki smtplib wyślą Państwo wiadomość w kilku linijkach, a Django pakuje to we własną warstwę pocztową z ustawieniami EMAIL. Czego nie dostarcza żadne z nich, to dostarczalność — ta zależy od serwera, przez który Państwo wysyłają.
Czy mają Państwo luźny skrypt, aplikację Flask czy projekt Django: ustawienia to te same pięć danych. Kierują je Państwo na swój relay, a kod budujący wiadomości zostaje bez zmian.
Dlaczego bez relaya coś idzie nie tak
Poczta z Pythona pochodzi często z tła: nocnego raportu, zadania cron wysyłającego faktury, skryptu ostrzegającego, gdy pomiar wypada poza normę. Nikt na to nie patrzy — a więc nikt nie zauważa, że serwer odbiorcy po cichu je odrzuca. Przy Django dochodzi klasyczna niespodzianka przy uruchomieniu: w rozwoju wszystko idzie na konsolę, a w produkcji do prawdziwych dostawców, którzy <em>faktycznie</em> sprawdzają reputację.
Konfiguracja SMTP w Python
Pobranie danych
Zaloguj się na swoje konto 2mail i pobierz stamtąd serwer SMTP, nazwę użytkownika oraz hasło.
Wpisanie w Python
W projekcie Django prosimy ustawić w settings dane EMAIL: backend SMTP jako backend pocztowy, host relaya, port 587, włączoną opcję STARTTLS, nazwę użytkownika i hasło oraz domyślny adres nadawcy we własnej domenie. Jeśli pracują Państwo bezpośrednio ze smtplib, prosimy połączyć się z hostem na porcie 587, wywołać starttls, zalogować się nazwą użytkownika i hasłem, a potem wysłać wiadomość. W obu przypadkach prosimy odczytywać dane ze zmiennych środowiskowych, a nie z kodu.
Uwierzytelnienie domeny
Zadbaj o poprawne ustawienie SPF, DKIM i DMARC dla swojej domeny, aby odbiorcy mogli zweryfikować, że wiadomość pochodzi od Ciebie.
Test
Wyślij ze swojej platformy wiadomość testową na adres u dużego dostawcy i sprawdź, czy trafia do skrzynki odbiorczej, a nie do spamu.
Pola, o które prosi Python
Każda platforma nazywa je nieco inaczej, ale zawsze chodzi o te same pięć. Konkretne wartości są powiązane z kontem i znajdą je Państwo na swoim koncie 2mail — prosimy je stamtąd przepisać, aby mieć pewność, że są właściwe.
| Pole | Co wpisać |
|---|---|
| Serwer SMTP (host) | Nazwa serwera z Państwa konta 2mail. |
| Port | Port odpowiadający wybranemu szyfrowaniu; Państwa konto pokazuje, który to. |
| Szyfrowanie | TLS. Nigdy nie wysyłają Państwo bez szyfrowania — inaczej dane logowania idą przez sieć otwartym tekstem. |
| Nazwa użytkownika | Login SMTP Państwa konta 2mail. |
| Hasło | Odpowiadające mu hasło. Nigdy nie należy używać hasła do skrzynki pracownika. |
Konfiguracja SMTP dla Python w kodzie
Działające przykłady dla portu 587 ze STARTTLS, gotowe do skopiowania. Prosimy zastąpić adres nadawcy adresem we własnej domenie oraz sprawdzić nazwę serwera i login na swoim koncie 2mail. Miejsce hasła jest w zmiennej środowiskowej — nigdy w kodzie i nigdy w systemie kontroli wersji.
smtplib: połączenie na porcie 587 ze starttls()
Biblioteka standardowa wystarczy. Kolejność ma znaczenie: najpierw starttls(), dopiero potem login() — inaczej Państwa hasło idzie przez sieć bez szyfrowania albo relay odrzuca logowanie. Prosimy używać smtplib.SMTP, a nie SMTP_SSL: ta druga klasa pasuje do portu 465. Login i hasło pochodzą ze zmiennych środowiskowych.
import os
import smtplib
import ssl
from email.message import EmailMessage
msg = EmailMessage()
msg["From"] = "Example <noreply@example.com>"
msg["To"] = "you@example.org"
msg["Subject"] = "SMTP test"
msg.set_content("Sent through the 2mail SMTP relay.")
context = ssl.create_default_context()
with smtplib.SMTP("relay.2mail.eu", 587, timeout=10) as server:
server.starttls(context=context) # upgrade to TLS first, log in afterwards
server.login(os.environ["SMTP_USERNAME"], os.environ["SMTP_PASSWORD"])
server.send_message(msg) # call again for the next message, same connection Django: ustawienia EMAIL_*
Django nie potrzebuje dodatkowego pakietu. Prosimy jawnie ustawić backend SMTP: w środowisku deweloperskim często ustawiony jest backend konsolowy, a wtedy produkcja niczego nie wysyła, choć wszystko wydaje się działać. sendtestemail to wbudowane polecenie administracyjne, którym sprawdzą Państwo ustawienia bez wywoływania widoku albo zadania.
# settings.py
import os
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_HOST = "relay.2mail.eu"
EMAIL_PORT = 587
EMAIL_USE_TLS = True # STARTTLS - never together with EMAIL_USE_SSL
EMAIL_HOST_USER = os.environ["SMTP_USERNAME"]
EMAIL_HOST_PASSWORD = os.environ["SMTP_PASSWORD"]
EMAIL_TIMEOUT = 10
DEFAULT_FROM_EMAIL = "Example <noreply@example.com>"
SERVER_EMAIL = "noreply@example.com" # sender of error reports to ADMINS
# Test from the shell:
# python manage.py sendtestemail you@example.org Flask-Mail: te same pięć danych
Jeśli pracują Państwo z Flaskiem, Flask-Mail pyta o te same dane pod innymi nazwami. Tu także obowiązuje zasada: MAIL_USE_TLS dla portu 587, a MAIL_USE_SSL pozostaje wyłączone.
import os
from flask import Flask
from flask_mail import Mail
app = Flask(__name__)
app.config.update(
MAIL_SERVER="relay.2mail.eu",
MAIL_PORT=587,
MAIL_USE_TLS=True, # STARTTLS
MAIL_USE_SSL=False,
MAIL_USERNAME=os.environ["SMTP_USERNAME"],
MAIL_PASSWORD=os.environ["SMTP_PASSWORD"],
MAIL_DEFAULT_SENDER="noreply@example.com",
)
mail = Mail(app) Jakie wiadomości wysyła Python
Gdy relay jest już skonfigurowany, wszystkie idą przez 2mail — nie trzeba ich konfigurować pojedynczo.
Pułapki przy Python
Opcja TLS i port muszą się zgadzać
Django zna dwa osobne ustawienia TLS: jedno dla STARTTLS i jedno dla TLS od samego początku. Prosimy włączyć wyłącznie ustawienie STARTTLS; ono pasuje do portu 587. Oba naraz dają błąd konfiguracji; żadne z nich wysyła Państwa login bez szyfrowania, a to relay słusznie odrzuca.
Jedno połączenie na serię, nie na wiadomość
Skrypt, który dla każdej z tysiąca wiadomości otwiera nowe połączenie SMTP, zachowuje się jak napastnik. Prosimy otworzyć jedno połączenie, wysłać serię i zamknąć. Django ma do tego funkcję wysyłania wielu wiadomości przez jedno połączenie.
Ciche błędy w zadaniach w tle
Wyjątek w zadaniu cron znika w pliku logu, którego nikt nie czyta. Prosimy przechwytywać błędy przy wysyłce i zgłaszać je tam, gdzie rzucają się w oczy — oraz podłączyć webhooki 2mail dla dostarczenia i odbicia, aby widzieć też, co psuje się <em>po</em> przekazaniu.
Częste komunikaty o błędach przy Python
Komunikaty, które zobaczą Państwo, gdy host, port, szyfrowanie albo login się nie zgadzają — przytoczone dosłownie, aby dało się je rozpoznać w logu, wraz z przyczyną i rozwiązaniem obok.
| Komunikat | Przyczyna | Rozwiązanie |
|---|---|---|
smtplib.SMTPAuthenticationError: (535, b'5.7.8 Error: authentication failed: …') | Relay odrzuca login: błędna nazwa użytkownika albo błędne hasło, albo zmienna środowiskowa, która w kontekście crona lub usługi ma inną wartość niż w Państwa powłoce. | Prosimy wypisać to, co proces faktycznie odczytuje (nazwę użytkownika, nigdy hasło), i użyć loginu SMTP z Państwa konta 2mail — a nie hasła do skrzynki pocztowej. |
smtplib.SMTPNotSupportedError: SMTP AUTH extension not supported by server. | login() jest wywoływane przed starttls(). Relay oferuje uwierzytelnianie dopiero wtedy, gdy połączenie jest szyfrowane; na jeszcze nieszyfrowanym połączeniu AUTH po prostu nie istnieje. | Prosimy najpierw wywołać server.starttls(context=context), a dopiero potem server.login(…). W Django: prosimy ustawić EMAIL_USE_TLS = True. |
ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] wrong version number (_ssl.c:…) | Kod używa smtplib.SMTP_SSL (albo, w Django, EMAIL_USE_SSL) na porcie 587. Python zaczyna wtedy od razu od uzgadniania TLS, podczas gdy relay na tym porcie oczekuje najpierw zwykłego SMTP i przełącza się dopiero po STARTTLS. Klasyczne pomylenie portów 465 i 587. | Prosimy użyć smtplib.SMTP(…, 587), a następnie starttls(), albo w Django EMAIL_USE_TLS = True z portem 587. |
smtplib.SMTPServerDisconnected: Connection unexpectedly closed | Połączenie zostało zerwane przed rozmową albo w jej trakcie. Zwykle to skrypt, który utrzymuje otwarte połączenie i po długiej przerwie używa go ponownie, podczas gdy serwer zdążył je zamknąć z powodu braku aktywności — albo zwykłe połączenie SMTP z portem, który oczekuje TLS od pierwszego bajtu. | Prosimy otwierać jedno połączenie na serię wiadomości i zamykać je po wysyłce; wyjątek prosimy przechwycić i połączyć się ponownie. Prosimy sprawdzić, czy używają Państwo portu 587 ze starttls(). |
ValueError: EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set one of those settings to True. | W ustawieniach Django włączone są obie opcje TLS — często dlatego, że jedna pochodzi ze wspólnego pliku bazowego, a druga z ustawień produkcyjnych. | Prosimy zostawić wyłącznie EMAIL_USE_TLS = True i ustawić EMAIL_USE_SSL = False. |
Inna platforma?
SMTP to protokół standardowy: każdy system, który mówi po SMTP — CRM, ERP, własna aplikacja albo serwer — może wysyłać przez 2mail z użyciem tych samych pięciu pól.