Comment envoyer un email avec Python?


170

Ce code fonctionne et m'envoie un e-mail très bien:

import smtplib
#SERVER = "localhost"

FROM = 'monty@python.com'

TO = ["jon@mycompany.com"] # must be a list

SUBJECT = "Hello!"

TEXT = "This message was sent with Python's smtplib."

# Prepare actual message

message = """\
From: %s
To: %s
Subject: %s

%s
""" % (FROM, ", ".join(TO), SUBJECT, TEXT)

# Send the mail

server = smtplib.SMTP('myserver')
server.sendmail(FROM, TO, message)
server.quit()

Cependant, si j'essaie de l'envelopper dans une fonction comme celle-ci:

def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
    import smtplib
    """this is some test documentation in the function"""
    message = """\
        From: %s
        To: %s
        Subject: %s
        %s
        """ % (FROM, ", ".join(TO), SUBJECT, TEXT)
    # Send the mail
    server = smtplib.SMTP(SERVER)
    server.sendmail(FROM, TO, message)
    server.quit()

et appelez-le, je reçois les erreurs suivantes:

 Traceback (most recent call last):
  File "C:/Python31/mailtest1.py", line 8, in <module>
    sendmail.sendMail(sender,recipients,subject,body,server)
  File "C:/Python31\sendmail.py", line 13, in sendMail
    server.sendmail(FROM, TO, message)
  File "C:\Python31\lib\smtplib.py", line 720, in sendmail
    self.rset()
  File "C:\Python31\lib\smtplib.py", line 444, in rset
    return self.docmd("rset")
  File "C:\Python31\lib\smtplib.py", line 368, in docmd
    return self.getreply()
  File "C:\Python31\lib\smtplib.py", line 345, in getreply
    raise SMTPServerDisconnected("Connection unexpectedly closed")
smtplib.SMTPServerDisconnected: Connection unexpectedly closed

Quelqu'un peut-il m'aider à comprendre pourquoi?


2
comment appelle-t-on la fonction?
Jochen Ritzel

L'indentation que vous avez publiée est-elle la même que celle que vous avez dans votre fichier?
gddc

@gddc non Je me suis assuré de l'indenter correctement, c'est juste la façon dont je l'ai collé.
cloud311

J'appelle la fonction en l'important dans mon module principal et en lui passant les paramètres que j'ai définis.
cloud311

4
Bien que la suggestion de @ Arrieta d'utiliser le package de courrier électronique soit le meilleur moyen de résoudre ce problème, votre approche peut fonctionner. Les différences entre vos deux versions sont dans la chaîne: (1) comme le souligne @NickODell, vous avez des espaces au début dans la version de la fonction. Les en-têtes ne doivent avoir aucun espace de début (sauf s'ils sont encapsulés). (2) à moins que TEXT n'inclue une ligne vierge, vous avez perdu le séparateur entre les en-têtes et le corps.
Tony Meyer

Réponses:


192

Je vous recommande d'utiliser les packages standard emailet smtplibd'envoyer des e-mails ensemble. Veuillez regarder l'exemple suivant (reproduit à partir de la documentation Python ). Notez que si vous suivez cette approche, la tâche "simple" est en effet simple, et les tâches les plus complexes (comme attacher des objets binaires ou envoyer des messages simples / HTML en plusieurs parties) sont accomplies très rapidement.

# Import smtplib for the actual sending function
import smtplib

# Import the email modules we'll need
from email.mime.text import MIMEText

# Open a plain text file for reading.  For this example, assume that
# the text file contains only ASCII characters.
with open(textfile, 'rb') as fp:
    # Create a text/plain message
    msg = MIMEText(fp.read())

# me == the sender's email address
# you == the recipient's email address
msg['Subject'] = 'The contents of %s' % textfile
msg['From'] = me
msg['To'] = you

# Send the message via our own SMTP server, but don't include the
# envelope header.
s = smtplib.SMTP('localhost')
s.sendmail(me, [you], msg.as_string())
s.quit()

Pour envoyer un e-mail à plusieurs destinations, vous pouvez également suivre l'exemple de la documentation Python :

# Import smtplib for the actual sending function
import smtplib

# Here are the email package modules we'll need
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart

# Create the container (outer) email message.
msg = MIMEMultipart()
msg['Subject'] = 'Our family reunion'
# me == the sender's email address
# family = the list of all recipients' email addresses
msg['From'] = me
msg['To'] = ', '.join(family)
msg.preamble = 'Our family reunion'

# Assume we know that the image files are all in PNG format
for file in pngfiles:
    # Open the files in binary mode.  Let the MIMEImage class automatically
    # guess the specific image type.
    with open(file, 'rb') as fp:
        img = MIMEImage(fp.read())
    msg.attach(img)

# Send the email via our own SMTP server.
s = smtplib.SMTP('localhost')
s.sendmail(me, family, msg.as_string())
s.quit()

Comme vous pouvez le voir, l'en-tête Tode l' MIMETextobjet doit être une chaîne composée d'adresses e-mail séparées par des virgules. D'autre part, le deuxième argument de la sendmailfonction doit être une liste de chaînes (chaque chaîne est une adresse e-mail).

Donc, si vous avez trois adresses e - mail: person1@example.com, person2@example.comet person3@example.com, vous pouvez le faire comme suit (sections évidentes omis):

to = ["person1@example.com", "person2@example.com", "person3@example.com"]
msg['To'] = ",".join(to)
s.sendmail(me, to, msg.as_string())

la ",".join(to)partie fait une seule chaîne de la liste, séparée par des virgules.

D'après vos questions, je suppose que vous n'avez pas parcouru le didacticiel Python - c'est un MUST si vous voulez aller n'importe où en Python - la documentation est principalement excellente pour la bibliothèque standard.


1
Merci, cela fonctionne très bien depuis une fonction, mais comment puis-je envoyer à plusieurs destinataires? Puisque msg [to] ressemble à une clé de dictionnaire, j'ai essayé de séparer le msg [to] par un point-virgule mais cela ne semble pas fonctionner.
cloud311

1
@ cloud311 exactement comme vous l'avez dans votre code. Il veut une chaîne délimitée par des virgules: ", ".join(["a@example.com", "b@example.net"])
Tim McNamara

3
Notez également que l'en-tête To: a une sémantique différente de celle du destinataire de l'enveloppe. Par exemple, vous pouvez utiliser '"Tony Meyer" <tony.meyer@gmail.com>' comme adresse dans l'en-tête To:, mais le destinataire de l'enveloppe doit être uniquement "tony.meyer@gmail.com". Pour créer une adresse «sympa» pour:, utilisez email.utils.formataddr, comme email.utils.formataddr ("Tony Meyer", "tony.meyer@gmail.com").
Tony Meyer

2
Petite amélioration: le fichier doit être ouvert en utilisant with: with open(textfile, 'rb') as fp:. La fermeture explicite peut être supprimée, car le withbloc fermera le fichier même si une erreur se produit à l'intérieur.
jpmc26

1
Pas spécifique à cette réponse, mais lors de la connexion à un serveur SMTP que vous ne contrôlez pas, vous devez envisager la possibilité qu'il soit inaccessible, lent, rejetant les connexions ou autre. Au niveau du code, vous obtiendrez une exception, mais vous devrez ensuite trouver un moyen de réessayer l'envoi un peu plus tard. Si vous parlez à votre propre sendmail / postfix, il se chargera de ce ré-envoi pour vous.
Ralph Bolton

67

Eh bien, vous voulez avoir une réponse à jour et moderne.

Voici ma réponse:

Quand j'ai besoin d'envoyer un mail en python, j'utilise l' API mailgun qui me donne beaucoup de mal à envoyer des mails triés. Ils ont une application / api merveilleuse qui vous permet d'envoyer gratuitement 10000 e-mails par mois.

L'envoi d'un e-mail serait comme ceci:

def send_simple_message():
    return requests.post(
        "https://api.mailgun.net/v3/YOUR_DOMAIN_NAME/messages",
        auth=("api", "YOUR_API_KEY"),
        data={"from": "Excited User <mailgun@YOUR_DOMAIN_NAME>",
              "to": ["bar@example.com", "YOU@YOUR_DOMAIN_NAME"],
              "subject": "Hello",
              "text": "Testing some Mailgun awesomness!"})

Vous pouvez également suivre les événements et bien plus encore, consultez le guide de démarrage rapide .

J'espère que vous trouvez ça utile!


3
C'est en effet bien plus "de cette date". Bien qu'il utilise une API externe. J'utiliserais personnellement quelque chose comme yagmail pour le garder interne.
PascalVKooten

6
@PascalvKooten Absolument amusant de suivre votre publicité constante pour yagmail (oui, Monsieur, je l'examinerai la prochaine fois, Monsieur;). Mais je trouve très déroutant que presque personne ne semble se soucier du problème des PO, mais suggère plutôt des solutions très différentes. C'est comme si je demandais comment changer les ampoules de ma smart 2009 et la réponse est: Achetez une vraie Mercedes ...
flaschbier

Super que ma mission ait une valeur d'amusement pour certains.
PascalVKooten

4
@flaschbier La raison pour laquelle personne ne se soucie du problème des OP est que le titre est erroné. "Comment envoyer un email avec Python?" est la vraie raison pour laquelle les gens viennent regarder lorsqu'ils cliquent sur cette question, et ils s'attendent à une réponse que yagmail peut fournir: gentille et courte. Voilà. Plus de publicité yagmail.
PascalVKooten

1
Juste pour dire que pour les clients de Mailgun, l'envoi de courrier par webservices est considérablement plus convivial en bande passante que via SMTP (surtout si vous utilisez des pièces jointes).
Ralph Bolton

44

Je voudrais vous aider à envoyer des courriels en conseillant le paquet yagmail (je suis le mainteneur, désolé pour la publicité, mais je pense que cela peut vraiment aider!).

Le code entier pour vous serait:

import yagmail
yag = yagmail.SMTP(FROM, 'pass')
yag.send(TO, SUBJECT, TEXT)

Notez que je fournis des valeurs par défaut pour tous les arguments, par exemple si vous voulez vous envoyer vous-même, vous pouvez omettre TO, si vous ne voulez pas de sujet, vous pouvez également l'omettre.

De plus, le but est également de rendre très facile l'attachement de code html ou d'images (et d'autres fichiers).

Là où vous mettez du contenu, vous pouvez faire quelque chose comme:

contents = ['Body text, and here is an embedded image:', 'http://somedomain/image.png',
            'You can also find an audio file attached.', '/local/path/song.mp3']

Wow, comme il est facile d'envoyer des pièces jointes! Cela prendrait environ 20 lignes sans yagmail;)

De plus, si vous le configurez une fois, vous n'aurez plus jamais à saisir le mot de passe (et à le conserver en toute sécurité). Dans votre cas, vous pouvez faire quelque chose comme:

import yagmail
yagmail.SMTP().send(contents = contents)

ce qui est beaucoup plus concis!

Je vous invite à jeter un œil au github ou à l'installer directement avec pip install yagmail.


4
cela devrait être la meilleure solution.
Richard de Ree

1
OMG, c'est un module de messagerie simple et doux . Merci!
pepoluan

1
Puis-je utiliser yagmailautre chose que Gmail? J'essaye d'utiliser pour mon propre serveur SMTP.
Anirban Nag 'tintinmj'

@dtgq Merci pour votre préoccupation. Personnellement, je ne vois pas le vecteur d'attaque. Si quelqu'un veut changer le fichier sous le chemin que vous voulez envoyer, peu importe si vous avez une Attachmentclasse; c'est toujours la même chose. S'ils peuvent changer votre code, alors ils peuvent faire tout ce qu'ils veulent de toute façon (avec / sans root, c'est la même chose pour l'envoi d'e-mails). Cela me semble être le typique "c'est pratique / magique donc ça doit être moins sûr". Je suis curieux de savoir quelle menace réelle voyez-vous?
PascalVKooten

14

Il y a un problème d'indentation. Le code ci-dessous fonctionnera:

import textwrap

def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
    import smtplib
    """this is some test documentation in the function"""
    message = textwrap.dedent("""\
        From: %s
        To: %s
        Subject: %s
        %s
        """ % (FROM, ", ".join(TO), SUBJECT, TEXT))
    # Send the mail
    server = smtplib.SMTP(SERVER)
    server.sendmail(FROM, TO, message)
    server.quit()


3
@geotheory la SERVERvariable transmise à la fonction est les informations d'identification de l'utilisateur.
Utilisateur du

Il n'est pas recommandé de reconstituer manuellement un message SMTP valide avec une simple interpolation de chaîne, et votre réponse contient un bogue qui illustre parfaitement cela. (Le corps doit commencer par une ligne vide pour que ce soit un message valide.) Pour tout ce qui implique des jeux de caractères autres que du texte brut dans les années 1980 7 bits en anglais uniquement US-ASCII (pièces jointes, internationalisation et autre support MIME) vous veulent vraiment utiliser une bibliothèque qui gère ces choses. La emailbibliothèque Python le fait raisonnablement bien (en particulier depuis la version 3.6) bien qu'elle nécessite toujours une certaine compréhension de ce que vous faites.
tripleee

7

Voici un exemple sur Python 3.x, bien plus simple que 2.x:

import smtplib
from email.message import EmailMessage
def send_mail(to_email, subject, message, server='smtp.example.cn',
              from_email='xx@example.com'):
    # import smtplib
    msg = EmailMessage()
    msg['Subject'] = subject
    msg['From'] = from_email
    msg['To'] = ', '.join(to_email)
    msg.set_content(message)
    print(msg)
    server = smtplib.SMTP(server)
    server.set_debuglevel(1)
    server.login(from_email, 'password')  # user & password
    server.send_message(msg)
    server.quit()
    print('successfully sent the mail.')

appelez cette fonction:

send_mail(to_email=['12345@qq.com', '12345@126.com'],
          subject='hello', message='Your analysis has done!')

ci-dessous ne peut que pour les utilisateurs chinois:

Si vous utilisez 126/163, 网易 邮箱, vous devez définir "客户 端 授权 密码", comme ci-dessous:

entrez la description de l'image ici

réf: https://stackoverflow.com/a/41470149/2803344 https://docs.python.org/3/library/email.examples.html#email-examples


4

Lors de l'indentation de votre code dans la fonction (ce qui est correct), vous avez également indenté les lignes de la chaîne de message brute. Mais le premier espace blanc implique le pliage (concaténation) des lignes d'en-tête, comme décrit dans les sections 2.2.3 et 3.2.3 de la RFC 2822 - Format de message Internet :

Chaque champ d'en-tête est logiquement une seule ligne de caractères comprenant le nom du champ, les deux points et le corps du champ. Pour des raisons de commodité cependant, et pour traiter les limitations de 998/78 caractères par ligne, la partie de corps de champ d'un champ d'en-tête peut être divisée en une représentation sur plusieurs lignes; c'est ce qu'on appelle le «pliage».

Dans la forme de fonction de votre sendmailappel, toutes les lignes commencent par un espace blanc et sont donc "dépliées" (concaténées) et vous essayez d'envoyer

From: monty@python.com    To: jon@mycompany.com    Subject: Hello!    This message was sent with Python's smtplib.

Autre que notre esprit ne le suggère, smtplibne comprendra plus les en To:- Subject:têtes et , car ces noms ne sont reconnus qu'au début d'une ligne. Au lieu de cela, smtplibil supposera une adresse e-mail d'expéditeur très longue:

monty@python.com    To: jon@mycompany.com    Subject: Hello!    This message was sent with Python's smtplib.

Cela ne fonctionnera pas et vient donc votre exception.

La solution est simple: il suffit de conserver la messagechaîne telle qu'elle était avant. Cela peut être fait par une fonction (comme Zeeshan l'a suggéré) ou tout de suite dans le code source:

import smtplib

def sendMail(FROM,TO,SUBJECT,TEXT,SERVER):
    """this is some test documentation in the function"""
    message = """\
From: %s
To: %s
Subject: %s

%s
""" % (FROM, ", ".join(TO), SUBJECT, TEXT)
    # Send the mail
    server = smtplib.SMTP(SERVER)
    server.sendmail(FROM, TO, message)
    server.quit()

Maintenant, le déploiement ne se produit pas et vous envoyez

From: monty@python.com
To: jon@mycompany.com
Subject: Hello!

This message was sent with Python's smtplib.

qui est ce qui fonctionne et ce qui a été fait par votre ancien code.

Notez que je préservais également la ligne vide entre les en-têtes et le corps pour accueillir la section 3.5 de la RFC (qui est obligatoire) et mettre l'inclusion en dehors de la fonction selon le guide de style Python PEP-0008 (qui est facultatif).


3

Cela met probablement des onglets dans votre message. Imprimez le message avant de le transmettre à sendMail.


1

Assurez-vous d'avoir autorisé l'expéditeur et le destinataire à envoyer des e-mails et à recevoir des e-mails de sources inconnues (sources externes) dans le compte de messagerie.

import smtplib

#Ports 465 and 587 are intended for email client to email server communication - sending email
server = smtplib.SMTP('smtp.gmail.com', 587)

#starttls() is a way to take an existing insecure connection and upgrade it to a secure connection using SSL/TLS.
server.starttls()

#Next, log in to the server
server.login("#email", "#password")

msg = "Hello! This Message was sent by the help of Python"

#Send the mail
server.sendmail("#Sender", "#Reciever", msg)

entrez la description de l'image ici


msgn'est pas un message SMTP valide et semblera simplement disparaître dans l'éther si votre serveur de messagerie l'accepte.
tripleee

0

Je pensais que j'avais mis mes deux morceaux ici puisque je viens de comprendre comment cela fonctionne.

Il semble que vous n'ayez pas le port spécifié dans vos paramètres de connexion SERVER, cela m'a un peu affecté lorsque j'essayais de me connecter à mon serveur SMTP qui n'utilise pas le port par défaut: 25.

Selon la documentation smtplib.SMTP, votre demande / réponse ehlo ou helo devrait être automatiquement prise en charge, vous ne devriez donc pas avoir à vous en soucier (mais cela pourrait être quelque chose à confirmer si tout le reste échoue).

Une autre chose à vous demander est avez-vous autorisé les connexions SMTP sur votre serveur SMTP lui-même? Pour certains sites comme GMAIL et ZOHO, vous devez réellement entrer et activer les connexions IMAP dans le compte de messagerie. Votre serveur de messagerie peut ne pas autoriser les connexions SMTP qui ne proviennent pas de 'localhost' peut-être? Quelque chose à examiner.

La dernière chose est que vous voudrez peut-être essayer d'initier la connexion sur TLS. La plupart des serveurs nécessitent désormais ce type d'authentification.

Vous verrez que j'ai bloqué deux champs TO dans mon courrier électronique. Les éléments de dictionnaire msg ['TO'] et msg ['FROM'] msg permettent aux informations correctes de s'afficher dans les en-têtes de l'e-mail lui-même, que l'on voit à la réception de l'e-mail dans les champs À / De (vous peut même être en mesure d'ajouter un champ Répondre à ici. Les champs TO et FROM eux-mêmes sont ce dont le serveur a besoin. Je sais que j'ai entendu parler de certains serveurs de messagerie rejetant des e-mails s'ils ne disposent pas des en-têtes d'e-mail appropriés.

C'est le code que j'ai utilisé, dans une fonction, qui fonctionne pour moi pour envoyer par courrier électronique le contenu d'un fichier * .txt en utilisant mon ordinateur local et un serveur SMTP distant (ZOHO comme indiqué):

def emailResults(folder, filename):

    # body of the message
    doc = folder + filename + '.txt'
    with open(doc, 'r') as readText:
        msg = MIMEText(readText.read())

    # headers
    TO = 'to_user@domain.com'
    msg['To'] = TO
    FROM = 'from_user@domain.com'
    msg['From'] = FROM
    msg['Subject'] = 'email subject |' + filename

    # SMTP
    send = smtplib.SMTP('smtp.zoho.com', 587)
    send.starttls()
    send.login('from_user@domain.com', 'password')
    send.sendmail(FROM, TO, msg.as_string())
    send.quit()

0

Il est à noter que le module SMTP prend en charge le gestionnaire de contexte, il n'est donc pas nécessaire d'appeler manuellement quit (), cela garantira qu'il est toujours appelé même s'il y a une exception.

    with smtplib.SMTP_SSL('smtp.gmail.com', 465) as server:
        server.ehlo()
        server.login(user, password)
        server.sendmail(from, to, body)

-1

En ce qui concerne votre code, il ne semble pas y avoir quoi que ce soit de fondamentalement incorrect, sauf que, on ne sait pas comment vous appelez réellement cette fonction. Tout ce que je peux penser, c'est que lorsque votre serveur ne répond pas, vous obtiendrez cette erreur SMTPServerDisconnected. Si vous recherchez la fonction getreply () dans smtplib (extrait ci-dessous), vous aurez une idée.

def getreply(self):
    """Get a reply from the server.

    Returns a tuple consisting of:

      - server response code (e.g. '250', or such, if all goes well)
        Note: returns -1 if it can't read response code.

      - server response string corresponding to response code (multiline
        responses are converted to a single, multiline string).

    Raises SMTPServerDisconnected if end-of-file is reached.
    """

consultez un exemple sur https://github.com/rreddy80/sendEmails/blob/master/sendEmailAttachments.py qui utilise également un appel de fonction pour envoyer un e-mail, si c'est ce que vous essayez de faire (approche DRY).

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.