gids · python3 smtp

Hoe implementeert een productteam SMTP in Python 3 veilig?

Implementeer SMTP in Python 3 achter een geautoriseerde serverworker, niet in de browser of in code die gebruikers kunnen beïnvloeden. Bouw berichten met EmailMessage, houd envelope-ontvangers gescheiden van zichtbare headers, maak een geverifieerde SSL-context aan, stel eindige verbindingstimeouts in en gebruik SMTP_SSL voor TLS vanaf het begin van de verbinding of SMTP.starttls() gevolgd door EHLO voor een expliciete upgrade. Laad credentials uit een secretmanager, roep send_message() aan, controleer de resultaten voor geweigerde ontvangers en leg de exacte uitkomst van de poging vast. Probeer alleen tijdelijke fouten opnieuw, met begrensde backoff, en beschouw SMTP-acceptatie nooit als bewijs van inboxplaatsing.

Definieer één geautoriseerde e-mailbewerking

Begin bij een goedgekeurd productevent, zoals accountverificatie, een ontvangstbewijs, een aangevraagde melding of een beveiligingsnotificatie. Sla een duurzame uitgaande job op voordat je een SMTP-verbinding opent. Die job bevat een stabiele sleutel voor het bedrijfsevent, de tenant, de berichtklasse, de templaterevisie, de goedgekeurde envelope-afzender en -ontvangers, de zichtbare From-identiteit, de grondslag van toestemming of noodzaak en het huidige suppressieresultaat. Browser-, mobiele, template- en gebruikersinvoer mag geen SMTP-hosts, credentials, envelope-afzenders, willekeurige headers of onbeperkte ontvangers kiezen. Autoriseer de aanroeper en tenant, valideer adressen, begrens het aantal ontvangers en bijlagen en voorkom newline-injectie. Claim een job één keer en houd een append-only geschiedenis van pogingen bij. smtplib in Python is een protocolclient; het biedt geen zakelijke idempotentie, tenantisolatie, toestemming, suppressie of duurzame wachtrij. Die controles horen bij de applicatie eromheen.

Bouw gestructureerde berichten met EmailMessage

Gebruik email.message.EmailMessage in plaats van header- en MIME-strings aan elkaar te plakken. Stel From, To, Subject en een stabiele correlatieheader van de applicatie in op basis van gevalideerde waarden, roep daarna set_content aan voor plaintext, add_alternative voor een HTML-deel waar nodig en add_attachment alleen voor expliciet ondersteunde bestandstypen en -groottes. Genereer tekst en HTML uit dezelfde goedgekeurde templaterevisie. Escape onbetrouwbare waarden voor hun uitvoercontext en render geen ruwe HTML van gebruikers. Zet geen secrets, toegangstokens, onnodige persoonsgegevens of interne databasesleutels in headers, onderwerpen, trackingvelden of bijlagenamen. Het email-pakket serialiseert volgens zijn policy en kan tijdens het flattenen MIME-grenzen genereren, dus onderteken of hash de uiteindelijke geserialiseerde vorm als latere integriteitscontroles van de exacte bytes afhangen. Houd de SMTP-envelope gescheiden: zichtbare To- en Cc-headers zijn bedoeld voor lezers, terwijl de lijst met transportontvangers de RCPT TO-commando's bepaalt.

Kies bewust tussen impliciete TLS en STARTTLS

Gebruik SMTP_SSL als de server TLS vanaf het begin van de verbinding vereist. Gebruik SMTP voor een cleartext-verbinding alleen als de gedocumenteerde serverworkflow een directe STARTTLS-upgrade vereist. Volgens de documentatie van smtplib in Python plaatst starttls de volgende SMTP-commando's binnen TLS en moet de client daarna opnieuw ehlo aanroepen. Authenticeer nooit vóór de vereiste TLS-upgrade. Maak de context aan met ssl.create_default_context, zodat certificaatvalidatie en hostnaamcontrole veilige standaardinstellingen voor clients gebruiken, en geef de verwachte serverhostnaam door via de normale verbinding van de library. Behandel ontbrekende STARTTLS-ondersteuning, een certificaatfout, een niet-overeenkomende hostnaam of een mislukte TLS-onderhandeling als harde stop wanneer versleuteling vereist is. Schakel verificatie niet uit en vervang de context niet door een ongeverifieerde om productie werkend te krijgen. TLS per hop beschermt de SMTP-verbinding, niet opgeslagen berichtinhoud, verwerking door de provider, opslag bij de ontvanger of de uiteindelijke mailbox.

Houd credentials server-side en afgebakend

Laad de SMTP-gebruikersnaam, het wachtwoord of token tijdens runtime uit een beheerde secretdienst. Zet ze nooit in versiebeheer, Docker-lagen, configuratie die in Git is gecommit, URL's, commandoregelargumenten, debuguitvoer, analytics, exceptierapporten, testsnapshots, notebooks, tickets of prompts. Kies liever een credential met een scope van één omgeving, afzenderdomein of toegestane workload dan een beheersgeheim voor het hele account. Scheid productie van ontwikkeling en CI. Maak rotatie routine: maak een vervanger aan, werk de worker bij, voer een gecontroleerde bezorgtest uit, bevestig het bewijs voor authenticatie en uitkomst en trek daarna de oude credential in. Beperk toegang tot het geheim tot het verzendproces en audit beheerleesacties. De login-methode van Python probeert de authenticatiemechanismen die de server aanbiedt; de applicatie moet nog steeds bepalen of de server, de verbindingsbeveiliging, het account en het mechanisme acceptabel zijn. Herhaalde authenticatiefouten horen het cohort te pauzeren en een onderzoek te starten, in plaats van snelle wachtwoordretries uit te lokken.

Gebruik expliciete timeouts en een begrensde levensduur van verbindingen

Geef een eindige timeout mee aan SMTP of SMTP_SSL, zodat verbindings- en blokkerende bewerkingen een worker niet eindeloos bezet houden. Pas een overkoepelende jobdeadline en een annuleringsbeleid toe, want één sockettimeout is geen volledige controle op de leeftijd in de wachtrij. Deel een SMTP-object niet tussen gelijktijdige taken, tenzij de toegang geserialiseerd is en de status aantoonbaar veilig is. Een eenvoudig ontwerp opent één verbinding voor een begrensde batch, begroet de server, zet zo nodig TLS op, authenticeert, dient een klein aantal berichten in, roept quit aan en gooit de verbinding weg na fouten of bij leeftijdslimieten. Hergebruik kan overhead verminderen, maar vergroot de dubbelzinnigheid na verbroken serververbindingen, timeouts of een gedeeltelijke status. Begrens het aantal berichten per verbinding en maak bewust opnieuw verbinding. Monitor de latency van verbindingen, TLS-onderhandeling, authenticatie, commandolatency, verbroken serververbindingen en de leeftijd van jobs zonder credentials of berichtinhoud te loggen. De SMTP-server kan limieten opleggen die los van Python veranderen.

Verstuur één bericht en bewaar de resultaten van gedeeltelijk geaccepteerde ontvangers

SMTP.sendmail gebruikt from_addr en to_addrs voor de transportenvelope en herschrijft geen berichtheaders. SMTP.send_message serialiseert een EmailMessage en leidt standaardwaarden af, tenzij expliciete envelopewaarden worden meegegeven. Geef in productiecode de goedgekeurde envelope-afzender en ontvangerlijst expliciet mee, zodat de afhandeling van Bcc en de tenantautorisatie ondubbelzinnig blijven. Python documenteert dat sendmail normaal terugkeert als minstens één ontvanger is geaccepteerd, en een dictionary teruggeeft met een item voor elke geweigerde ontvanger. Het uitblijven van een exceptie betekent dus niet dat alle ontvangers zijn geaccepteerd. Sla de geaccepteerde en geweigerde ontvangers apart op, inclusief statuscode en opgeschoonde diagnostiek. Probeer geaccepteerde ontvangers niet opnieuw als alleen sommige ontvangers zijn geweigerd. Behandel elke ontvanger als een zelfstandig geautoriseerde uitkomst, met behoud van de gedeelde berichtpoging. Een latere exceptie in de DATA-fase is iets anders dan een RCPT-weigering en heeft een eigen classificatie nodig.

Classificeer excepties op fase en permanentie

Handel smtplib-excepties expliciet af en bewaar hun SMTP-codes en opgeschoonde serverberichten. SMTPConnectError en timeouts kunnen tijdelijk zijn, maar kunnen ook wijzen op een verkeerde host, poort, firewall of storing. SMTPNotSupportedError na STARTTLS of SMTPUTF8 moet een configuratie stoppen die die functie vereist. SMTPAuthenticationError vraagt om onderzoek naar credentials, account, mechanisme en TLS, niet om blinde retries. SMTPSenderRefused en SMTPRecipientsRefused vragen om beslissingen per identiteit of ontvanger. SMTPDataError beschrijft een onverwachte DATA-respons en kan, afhankelijk van de enhanced status, wijzen op inhoud, beleid, quotum of tijdelijk gedrag van de ontvanger. Classificeer 4xx-responses als kandidaten voor begrensde retries en 5xx als permanent voor die poging, met inachtneming van providerspecifieke documentatie. Gebruik exponential backoff, jitter, limieten op pogingen en leeftijd in de wachtrij en een dead-letter-status. Probeer nooit opnieuw na suppressie, een klacht, afmelding, ingetrokken autorisatie of bewijs van een ongeldige ontvanger.

Reconcilieer dubbelzinnige submissionuitkomsten

Een netwerktimeout of verbroken verbinding nadat de client de berichtdata heeft verzonden, maar voordat het laatste serverantwoord binnen is, is dubbelzinnig. De server kan de verantwoordelijkheid hebben overgenomen, ook al gaf Python een exceptie. Maak niet meteen een nieuwe logische verzending aan. Markeer de poging als onbekend, behoud de stabiele event- en trace-ID's en raadpleeg providerlogs of latere bezorgevents als die beschikbaar zijn. Als de SMTP-dienst geen idempotentie of doorzoekbare correlatie biedt, neem dan een productbeslissing op basis van berichtklasse, leeftijd, schade door duplicaten en gebruikerservaring. Beveiligingsmeldingen en wachtwoordresetberichten hebben andere risico's bij duplicaten dan ontvangstbewijzen of financiële berichten. Bewaar de oorspronkelijke poging en een eventuele retry-koppeling in het grootboek. Claim nooit exactly-once-bezorging, want SMTP biedt dat niet end-to-end. Test deze tak met een gecontroleerde serverfixture die de verbinding in elke protocolfase verbreekt, ook vóór en na acceptatie van DATA.

Scheid SMTP-acceptatie van bezorging en betrokkenheid

Een geslaagde aanroep van send_message betekent dat minstens één ontvanger in de waargenomen SMTP-fase is geaccepteerd, volgens de gedocumenteerde semantiek van Python. Het bewijst niet dat elke ontvanger is geaccepteerd, dat de bestemmingsserver het bericht later heeft behouden, dat het in een inboxmap is beland of dat iemand het heeft gelezen. Modelleer submission bij de provider, acceptatie door de server van de ontvanger, tijdelijke of permanente fouten, latere bounces, klachten, afmeldingen, plaatsing in de mailbox en betrokkenheid als afzonderlijk bewijs. Verwerk geauthenticeerde providerevents als die beschikbaar zijn, ontdubbel ze en houd het tijdstip van optreden gescheiden van het tijdstip van verwerking. Dwing permanente bounces, klachten en afmeldingen direct af vóór latere verzendingen. Opens en clicks zijn geen transportbewijs en kunnen door privacytechnologie worden beïnvloed. Houd geaggregeerde metrieken met zo min mogelijk persoonsgegevens bij per tenantveilig cohort, templaterevisie, afzenderdomein, statusklasse en tijd. Stel meldingen in voor pieken in weigeringen, onbekende uitkomsten, leeftijd van de wachtrij, TLS-fouten, authenticatiefouten en ongebruikelijke spreiding over ontvangers.

Test lokaal zonder echte klantmail te versturen

Voer unit-tests uit voor berichtopbouw, afwijzing van headerinjectie, autorisatie van ontvangers, verwijdering van Bcc, plaintext- en HTML-alternatieven, Unicode-afhandeling, bijlagelimieten en suppressiecontroles. Gebruik een gecontroleerde lokale SMTP-testserver of protocolfixture om fouten bij de begroeting, ontbrekende STARTTLS, certificaatfouten, authenticatiefouten, gedeeltelijke RCPT-acceptatie, DATA-4xx- en -5xx-antwoorden, verbroken verbindingen en vertraagde responses te simuleren. Gebruik geen verouderde niet-geauthenticeerde debuggingdiensten voor geheimen of klantinhoud die op productie lijken. Integratietests moeten specifieke accounts en gecontroleerde ontvangers gebruiken, met expliciete quota en opschoning. Verifieer het ruwe ontvangen bericht, authenticatieresultaten, zichtbare headers, antwoordgedrag en eventcorrelatie. Voer secret scanning uit op fixtures en logs.

Hoe SendHQ past

SendHQ documenteert een e-mail-API per workspace voor verzending vanaf geverifieerde domeinen, bezorgevents en suppressies. Deze gids behandelt de SMTP-client uit de standaardbibliotheek van Python; gebruik de documentatie van SendHQ voor de actuele integratiemethoden en het API-contract.

Veelgestelde vragen

Mogen SMTP-credentials voor Python in clientcode staan?

Nee. Bewaar ze in een server-side secretmanager met een smalle scope per omgeving en workload, geauditeerde toegang, routinematige rotatie en zonder logging.

Wanneer gebruik je in Python SMTP_SSL?

Gebruik SMTP_SSL als TLS vanaf het begin van de verbinding vereist is. Gebruik SMTP plus starttls alleen voor een gedocumenteerde workflow met expliciete upgrade die gesloten faalt.

Moet je EHLO opnieuw aanroepen na starttls?

Ja. Volgens de documentatie van smtplib in Python roep je ehlo opnieuw aan na starttls, zodat de mogelijkheden binnen de beveiligde verbinding opnieuw worden opgevraagd.

Betekent send_message dat elke ontvanger is geaccepteerd?

Nee. Python kan normaal terugkeren als minstens één ontvanger is geaccepteerd en geeft geweigerde ontvangers apart terug. Sla uitkomsten per ontvanger op en handel ze zelfstandig af.

Wat moet er gebeuren na een SMTPAuthenticationError?

Pauzeer de betrokken configuratie en controleer TLS, server, account, geheim en de aangeboden mechanismen. Blinde retries met credentials kunnen blokkades of signalen van een compromittering versterken.

Moet je elke SMTPDataError opnieuw proberen?

Nee. Bewaar de exacte status en diagnostiek en maak daarna onderscheid tussen tijdelijke 4xx-situaties en permanente 5xx-fouten in beleid, inhoud, quotum of configuratie.

Bepaalt SMTP-acceptatie de inboxplaatsing?

Nee. Het is transportbewijs met een beperkte reikwijdte. Latere relays, filtering door de ontvanger, bounces, mailboxregels, plaatsing in een map en menselijke betrokkenheid blijven aparte uitkomsten.

Behandelt deze gids SendHQ-specifieke integratie?

Nee. Deze gids behandelt de SMTP-client uit de standaardbibliotheek van Python. Raadpleeg de documentatie van SendHQ voor de actuele integratiemethoden en het API-contract.

Bronnen