¿Cómo enviar un correo electrónico con Python?

170

Este código funciona y me envía un correo electrónico muy bien:

import smtplib
#SERVER = "localhost"

FROM = '[email protected]'

TO = ["[email protected]"] # 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()

Sin embargo, si trato de envolverlo en una función como esta:

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()

y lo llamo me sale los siguientes errores:

 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

¿Alguien puede ayudarme a entender por qué?

cloud311
fuente
2
¿Cómo se llama la función?
Jochen Ritzel
¿La sangría que publicó es la misma que tiene en su archivo?
gddc
@gddc sin que se aseguró de guión correctamente, que es como a mí me pegué en.
cloud311
Llamo a la función importándola a mi módulo principal y pasando los parámetros que he definido en ella.
cloud311
44
Aunque la sugerencia de @ Arrieta de usar el paquete de correo electrónico es la mejor manera de resolver esto, su enfoque puede funcionar. Las diferencias entre sus dos versiones están en la cadena: (1) como señala @NickODell, tiene espacios en blanco iniciales en la versión de la función. Los encabezados no deben tener espacio inicial (a menos que estén envueltos). (2) a menos que TEXT incluya una línea en blanco inicial, ha perdido el separador entre encabezados y cuerpo.
Tony Meyer

Respuestas:

192

Le recomiendo que use los paquetes estándar emaily smtplibjuntos para enviar correos electrónicos. Mire el siguiente ejemplo (reproducido de la documentación de Python ). Tenga en cuenta que si sigue este enfoque, la tarea "simple" es realmente simple, y las tareas más complejas (como adjuntar objetos binarios o enviar mensajes multiparte sin formato / HTML) se realizan muy rápidamente.

# 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()

Para enviar correos electrónicos a múltiples destinos, también puede seguir el ejemplo en la documentación de 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()

Como puede ver, el encabezado Todel MIMETextobjeto debe ser una cadena que consta de direcciones de correo electrónico separadas por comas. Por otro lado, el segundo argumento de la sendmailfunción debe ser una lista de cadenas (cada cadena es una dirección de correo electrónico).

Por lo tanto, si tiene tres direcciones de correo electrónico: [email protected], [email protected], y [email protected], se puede hacer de la siguiente manera (secciones obvias omitidas):

to = ["[email protected]", "[email protected]", "[email protected]"]
msg['To'] = ",".join(to)
s.sendmail(me, to, msg.as_string())

la ",".join(to)parte forma una sola cadena fuera de la lista, separada por comas.

De sus preguntas, deduzco que no ha seguido el tutorial de Python , es imprescindible si desea llegar a cualquier lugar de Python, la documentación es en su mayoría excelente para la biblioteca estándar.

Escualo
fuente
1
Gracias, esto funciona muy bien desde una función, sin embargo, ¿cómo puedo enviar a múltiples destinatarios? Como parece que msg [to] es una clave de diccionario, intenté separar el msg [to] con un punto y coma, pero eso no parece funcionar.
cloud311
1
@ cloud311 exactamente como lo tiene en su código. Quiere una cadena delimitada con comas: ", ".join(["[email protected]", "[email protected]"])
Tim McNamara
3
Tenga en cuenta también que el encabezado To: tiene una semántica diferente a la del destinatario del sobre. Por ejemplo, puede usar '"Tony Meyer" <[email protected]>' como una dirección en el encabezado Para: pero el destinatario del sobre debe ser solo "[email protected]". Para construir una dirección 'agradable' Para:, use email.utils.formataddr, como email.utils.formataddr ("Tony Meyer", "[email protected]").
Tony Meyer
2
Pequeña mejora: el archivo se debe abrir usando with: with open(textfile, 'rb') as fp:. El cierre explícito se puede descartar, ya que el withbloque cerrará el archivo incluso si se produce un error dentro de él.
jpmc26
1
No es específico de esta respuesta, pero al conectarse a cualquier servidor SMTP que no controle, debe considerar la posibilidad de que sea inaccesible, lento, rechace conexiones o cualquier otra cosa. En cuanto al código, obtendrá una excepción, pero luego deberá encontrar la forma de volver a intentar el envío un poco más adelante. Si habla con su propio sendmail / postfix, se encargará de ese reenvío por usted.
Ralph Bolton
67

Bueno, desea tener una respuesta actualizada y moderna.

Aquí está mi respuesta:

Cuando necesito enviar un correo electrónico a Python, utilizo la API de mailgun , que me causa muchos dolores de cabeza al enviar correos ordenados. Tienen una aplicación / api maravillosa que te permite enviar 10,000 correos electrónicos por mes de forma gratuita.

Enviar un correo electrónico sería así:

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": ["[email protected]", "YOU@YOUR_DOMAIN_NAME"],
              "subject": "Hello",
              "text": "Testing some Mailgun awesomness!"})

También puede realizar un seguimiento de eventos y mucho más, consulte la guía de inicio rápido .

¡Espero que encuentres esto útil!

Dennis Decoene
fuente
3
De hecho, esto es mucho más "de esta fecha". Aunque utiliza una API externa. Yo personalmente usaría algo como yagmail para mantenerlo interno.
PascalVKooten
66
@PascalvKooten Absolutamente divertido seguir su constante publicidad de yagmail (sí, señor, lo consideraré la próxima vez, señor;). Pero me resulta muy confuso que casi nadie parezca preocuparse por el problema de los OP, sino que sugiere soluciones muy diferentes. Es como si estuviera preguntando cómo cambiar las bombillas en mi 2009 inteligente y la respuesta es: comprar un Mercedes real ...
flaschbier
Genial que mi misión tenga un valor divertido para algunos.
PascalVKooten
44
@flaschbier La razón por la que a nadie le importa el problema de los OP es porque el título es incorrecto. "¿Cómo enviar un correo electrónico con Python?" es la razón real por la que las personas acuden a mirar cuando hacen clic en esa pregunta, y esperan una respuesta que yagmail puede proporcionar: agradable y breve. Ahí tienes. Más publicidad de yagmail.
PascalVKooten
1
Solo para decir que para los clientes de Mailgun, el envío de correo por servicios web es mucho más amigable con el ancho de banda que a través de SMTP (especialmente si se usan archivos adjuntos).
Ralph Bolton
44

Me gustaría ayudarlo con el envío de correos electrónicos informando sobre el paquete de yagmail (soy el encargado de mantener, lo siento por la publicidad, ¡pero creo que realmente puede ayudar!).

El código completo para ti sería:

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

Tenga en cuenta que proporciono valores predeterminados para todos los argumentos, por ejemplo, si desea enviarse a sí mismo, puede omitir TO, si no desea un asunto, también puede omitirlo.

Además, el objetivo también es hacer que sea realmente fácil adjuntar código html o imágenes (y otros archivos).

Donde pones contenidos puedes hacer algo como:

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']

¡Qué fácil es enviar archivos adjuntos! Esto tomaría como 20 líneas sin yagmail;)

Además, si lo configura una vez, nunca tendrá que volver a ingresar la contraseña (y guardarla de forma segura). En tu caso puedes hacer algo como:

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

lo cual es mucho más conciso!

Te invito a echar un vistazo a Github o instalarlo directamente con pip install yagmail.

PascalVKooten
fuente
44
Esta debería ser la mejor solución.
Richard de Ree
1
Dios mío, es un dulce y sencillo módulo de correo electrónico. ¡Gracias!
pepoluan
1
¿Puedo usar yagmailotro que no sea gmail? Estoy tratando de usar para mi propio servidor SMTP.
Anirban Nag 'tintinmj'
@dtgq Gracias por su preocupación. Personalmente, no veo el vector de ataque. Si alguien va a cambiar el archivo en la ruta que desea enviar, no importa si tiene una Attachmentclase; Sigue siendo lo mismo. Si pueden cambiar su código, pueden hacer lo que quieran de todos modos (con / sin root, ese es el mismo envío de correo electrónico wrt). Esto me parece el típico "es conveniente / mágico, por lo que debe ser menos seguro". Tengo curiosidad por saber qué amenaza real ves.
PascalVKooten
14

Hay un problema de sangría. El siguiente código funcionará:

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()

Zeeshan
fuente
3
@geotheory la SERVERvariable que se pasa a la función son las credenciales de usuario.
Usuario
No se recomienda unir manualmente un mensaje SMTP válido con una simple interpolación de cadenas, y su respuesta tiene un error que ilustra esto perfectamente. (El cuerpo debe comenzar con una línea vacía para que este sea un mensaje válido). Para cualquier cosa que involucre juegos de caracteres que no sean texto sin formato en 1980 ASCII de 7 bits solo en inglés de EE. UU. (Archivos adjuntos, internacionalización y otro soporte MIME) Realmente quiero usar una biblioteca que maneje estas cosas. La emailbiblioteca de Python hace esto razonablemente bien (particularmente desde 3.6) aunque aún requiere cierta comprensión de lo que está haciendo.
tripleee
7

Aquí hay un ejemplo en Python 3.x, mucho más simple que 2.x:

import smtplib
from email.message import EmailMessage
def send_mail(to_email, subject, message, server='smtp.example.cn',
              from_email='[email protected]'):
    # 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.')

llama a esta función:

send_mail(to_email=['[email protected]', '[email protected]'],
          subject='hello', message='Your analysis has done!')

a continuación puede solo para usuarios chinos:

Si usa 126/163, 网易 邮箱, debe configurar "客户 端 授权 密码", como se muestra a continuación:

ingrese la descripción de la imagen aquí

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

Belter
fuente
4

Al sangrar su código en la función (que está bien), también sangró las líneas de la cadena de mensaje sin procesar. Pero el espacio en blanco inicial implica el plegado (concatenación) de las líneas de encabezado, como se describe en las secciones 2.2.3 y 3.2.3 de RFC 2822 - Formato de mensaje de Internet :

Cada campo de encabezado es lógicamente una sola línea de caracteres que comprende el nombre del campo, los dos puntos y el cuerpo del campo. Sin embargo, por conveniencia, y para lidiar con las limitaciones de caracteres 998/78 por línea, la parte del cuerpo del campo de un campo de encabezado se puede dividir en una representación de varias líneas; Esto se llama "plegado".

En la forma de la función de su sendmailllamada, todas las líneas comienzan con espacios en blanco y, por lo tanto, están "desplegadas" (concatenadas) y está intentando enviar

From: [email protected]    To: [email protected]    Subject: Hello!    This message was sent with Python's smtplib.

Aparte de lo que sugiere nuestra mente, smtplibya no entenderá los encabezados To:y Subject:, porque estos nombres solo se reconocen al comienzo de una línea. En smtplibsu lugar , asumirá una dirección de correo electrónico del remitente muy larga:

[email protected]    To: [email protected]    Subject: Hello!    This message was sent with Python's smtplib.

Esto no funcionará y entonces viene tu Excepción.

La solución es simple: simplemente conserva la messagecadena como estaba antes. Esto puede hacerse mediante una función (como sugirió Zeeshan) o de inmediato en el código fuente:

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()

Ahora el despliegue no ocurre y envías

From: [email protected]
To: [email protected]
Subject: Hello!

This message was sent with Python's smtplib.

que es lo que funciona y lo que hizo tu antiguo código.

Tenga en cuenta que también estaba preservando la línea vacía entre los encabezados y el cuerpo para acomodar la sección 3.5 del RFC (que se requiere) y puse el incluir fuera de la función de acuerdo con la guía de estilo Python PEP-0008 (que es opcional).

flaschbier
fuente
3

Probablemente esté poniendo pestañas en su mensaje. Imprima el mensaje antes de pasarlo a sendMail.

Nick ODell
fuente
1

Asegúrese de haber otorgado permiso para que el remitente y el receptor envíen correos electrónicos y reciban correos electrónicos de fuentes desconocidas (fuentes externas) en la cuenta de correo electrónico.

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)

ingrese la descripción de la imagen aquí

Devanand Sharma
fuente
msgno es un mensaje SMTP válido, y simplemente parecerá desaparecer en el éter si su servidor de correo lo acepta.
tripleee
0

Pensé en poner mis dos partes aquí ya que acabo de descubrir cómo funciona esto.

Parece que no tiene el puerto especificado en la configuración de conexión de su SERVIDOR, esto me afectó un poco cuando intentaba conectarme a mi servidor SMTP que no usa el puerto predeterminado: 25.

De acuerdo con los documentos smtplib.SMTP, su solicitud / respuesta ehlo o helo debe ser atendida automáticamente, por lo que no debería tener que preocuparse por esto (pero podría ser algo para confirmar si todo lo demás falla).

Otra cosa que debe preguntarse es si ha permitido conexiones SMTP en su servidor SMTP. Para algunos sitios como GMAIL y ZOHO, debe ingresar y activar las conexiones IMAP dentro de la cuenta de correo electrónico. ¿Es posible que su servidor de correo no permita conexiones SMTP que no provienen de 'localhost'? Algo a tener en cuenta.

Lo último es que puede intentar iniciar la conexión en TLS. La mayoría de los servidores ahora requieren este tipo de autenticación.

Verás que he atascado dos campos TO en mi correo electrónico. Los elementos del diccionario msg ['TO'] y msg ['FROM'] permiten que se muestre la información correcta en los encabezados del correo electrónico, que se ve en el extremo receptor del correo electrónico en los campos Para / De (usted incluso podría agregar un campo Responder a aquí. Los campos TO y FROM son lo que requiere el servidor. Sé que he oído hablar de algunos servidores de correo electrónico que rechazan los correos electrónicos si no tienen los encabezados de correo electrónico adecuados.

Este es el código que he usado, en una función, que me funciona para enviar por correo electrónico el contenido de un archivo * .txt usando mi computadora local y un servidor SMTP remoto (ZOHO como se muestra):

def emailResults(folder, filename):

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

    # headers
    TO = '[email protected]'
    msg['To'] = TO
    FROM = '[email protected]'
    msg['From'] = FROM
    msg['Subject'] = 'email subject |' + filename

    # SMTP
    send = smtplib.SMTP('smtp.zoho.com', 587)
    send.starttls()
    send.login('[email protected]', 'password')
    send.sendmail(FROM, TO, msg.as_string())
    send.quit()
ntk4
fuente
0

Vale la pena señalar que el módulo SMTP es compatible con el administrador de contexto, por lo que no es necesario llamar manualmente a quit (), esto garantizará que siempre se llame incluso si hay una excepción.

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

En lo que respecta a su código, no parece haber nada fundamentalmente incorrecto, excepto que no está claro cómo llama realmente a esa función. Todo lo que puedo pensar es que cuando su servidor no responde, obtendrá este error SMTPServerDisconnected. Si busca la función getreply () en smtplib (extracto a continuación), obtendrá una idea.

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.
    """

consulte un ejemplo en https://github.com/rreddy80/sendEmails/blob/master/sendEmailAttachments.py que también utiliza una llamada de función para enviar un correo electrónico, si eso es lo que está tratando de hacer (enfoque DRY).

rreddy
fuente