miércoles, 11 de noviembre de 2020

¿Cómo enviar y leer e-mails en python?


 








¿Cómo enviar e-mails en python?


En este post vamos a ver como se puede enviar e-mails usando python de una forma sencilla. El ejemplo esta hecho con outlook, porque aunque intente lo mismo con gmail y yahoo a mi no me funcionó (creo que hay que modificar cuestiones de seguridad en ambos si quieres que funcione). Puedes crearte una cuenta gratuita en https://outlook.live.com/owa/

No obstante si quieres usar gmail puedes ver información sobre la API de gmail para python aqui.

- Enviando un e-mail solamente de texto (Texto Plano)


Lo primero es crear el archivo de Python con el nombre que quieras, en mi caso lo llamaré correo_texto_plano.py

Vamos a crear un programa que envíe sencillamente un mensaje en texto plano, sin ningún tipo de formato. Para ello empezaremos importando las bibliotecas que vamos a necesitar. Están dentro de las bibliotecas generales de Python por lo que no hace falta instalarlas.


# importamos los paquetes necesarios.
 
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib

A continuación creamos una instancia del objeto MIMEMultipart y lo asignamos a la variable msg o como quieras llamarla.

# creamos una instancia del objeto MIMEMultipart
msg = MIMEMultipart()

Ahora creamos el mensaje que queremos mandar:

mensaje = "Enviando un mensaje de texto plano desde python"

Lo agregamos al cuerpo del mensaje como texto plano

msg.attach(MIMEText(mensaje, 'plain'))


Lógicamente tenemos que decirle al programa que correo es el que lo manda, a quien va dirigido, cual es el asunto y nuestra contraseña de la cuenta de outlook.

# Configuramos los parámetros del mensaje
password = "la password que tengas de tu cuenta de outlook"
msg['From'] = "tu_correo@outlook.com" #tu dirección de correo
msg['To'] = "aquien_va_dirigido@gmail.com" #a quien va dirigido
msg['Subject'] = "Primer envío - prueba" #el asunto del correo


Creamos e iniciamos el servidor:

server = smtplib.SMTP('smtp.outlook.com')
 
server.starttls()


Nos conectamos al servidor para poder enviar el correo:

server.login(msg['From'], password)


y enviamos el mensaje a través del servidor:

server.sendmail(msg['From'], msg['To'], msg.as_string())


Para finalizar cerrando la conexión y mostrando por pantalla un mensaje que nos confirme que el correo ha sido enviado.

server.quit()
 
print ("E-mail enviado con exito a %s:" % (msg['To']))

Ejecuta el programa para ver que funciona con:

$ python3 correo_texto_plano.py

Puedes encontrar el código del programa aquí.


- Enviando un e-mail de texto_plano y HTML.


En este ejemplo enviaremos un correo con texto plano, pero también una versión alternativa usando html. Verás como dependiendo del orden se enviará uno u otro. El proceso es muy similar al caso anterior.

Empezaremos importando los paquetes que vamos a necesitar, que son los mismos que en el ejemplo anterior  y que están en la biblioteca general de python. No es necesario instalar nada.

# Importamos los paquetes necesarios.

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import smtplib

A continuación creamos una instancia del objeto MIMEMultipart y lo asignamos a la variable msg pero en este caso el tipo de MIME que utilizaremos será "alternative":

# creamos una instancia del objeto MIMEMultipart (alternative)
msg = MIMEMultipart('alternative')

Creamos el cuerpo del mensaje. Va a ser texto plano y una versión del mensaje en html.

# Creamos el cuerpo del mensaje. Va a ser texto plano y otro mensaje en html.
text = "¡Hola!\n¿Cómo estás?\nAquí esta el link que querías:\nhttps://novatilloenpython.blogspot.com/"
html = """\
<html>
  <head></head>
  <body>
    <p>¡Hola!<br>
       ¿Cómo estás?<br>
       Aquí tienes el link que querías <a href="https://novatilloenpython.blogspot.com/">link</a>.
    </p>
  </body>
</html>
"""


Guardamos los tipos MIME de ambas partes, texto-plano y html:

part1 = MIMEText(text, 'plain')
part2 = MIMEText(html, 'html')

Agregamos ambas partes a nuestro contenedor de los mensajes (msg).

 

Según la especificación RFC 2046, la última parte de un mensaje que tiene varias partes -multiparte-, en este caso la parte html será la que se utilice preferentemente.

 

msg.attach(part1)
msg.attach(part2)

Configuramos los parámetros del mensaje (password, remitente, destinatario, asunto)

#Configuramos los parámetros del mensaje 
msg['From'] = "tu_correo@outlook.com"
msg['To'] = "a_quien_va_dirigido@gmail.com"
msg['Subject'] = "Texto Plano y HTML"
password = "La contraseña de tu cuenta de correo"


El resto de pasos son los mismo ya que en el punto anterior.

# Enviamos el mensaje a través del servidor SMTP local.
server = smtplib.SMTP('smtp.outlook.com')

server.starttls()

# Nos conectamos y logeamos en el servidor para enviar el correo
server.login(msg['From'], password)

# la función sendmail tiene 3 argumentos: dirección del remitente, dirección del receptor
# y mensaje a enviar - Aqui se envia como un único string.
server.sendmail(msg['From'], msg['To'], msg.as_string())
server.quit()

print ("E-mail enviado correctamente a %s:" % (msg['To']))

Si ahora ejecutas el programa el correo será enviado y el receptor verá la parte de html.

Puedes encontrar el código de este módulo aquí.


- Enviando un e-mail con una imagen.

Puede ser interesante el poder enviar imágenes a través de email. Ponte en la situación de que tienes un proyecto de seguridad en el que una cámara toma una imagen al detectar un intruso y te la envía por correo electrónico.

El proceso es muy similar al de los casos anteriores. Aquí añadiremos una importación más de MIME, que es MIMEImage.

Comenzamos creando el archivo que contendrá el mensaje. Lo llamaré Envio_imagen_email.py y ponemos en el mismo directorio la imagen que queramos enviar (imagen.jpeg en mi caso)

# Importamos los paquetes necesarios.

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.image import MIMEImage
import smtplib

A continuación creamos una instancia del objeto MIMEMultipart y lo asignamos a la variable msg.


# creamos una instancia del objeto MIMEMultipart
msg = MIMEMultipart()

Adjuntamos la imagen al cuerpo del mensaje. En mi caso la imagen (imagen.jpeg) está en el mismo directorio que el archivo de python.

file = open('imagen.jpeg', 'rb')
imagen = MIMEImage(file.read())
file.close()
msg.attach(imagen)

Ten en cuenta que la imagen es un archivo binario con lo cual lo abrimos con la opción "rb" de read binary. Luego asignamos su contenido a la variable "imagen" usando la instrucción MIMEImage(file.read()) y cerramos el archivo una vez realizado el paso anterior. Finalizamos añadiendo la imagen al cuerpo del mensaje.

El resto del código es exactamente igual que para los casos anteriores:

# configuramos los parámetros del mensaje
password = "la contraseña de tu correo"
msg['From'] = "tu_correo@outlook.com"
msg['To'] = "correo_del_destinatario@gmail.com"
msg['Subject'] = "Envío de una imagen adjunta al correo"

# creamos e iniciamos el servidor
server = smtplib.SMTP('smtp.outlook.com')

server.starttls()

# Nos logeamos en el servidor para poder enviar el e-mail
server.login(msg['From'], password)

# Enviamos el mensaje a través del servidor.
server.sendmail(msg['From'], msg['To'], msg.as_string())

server.quit()

print("E-mail enviado correctamente a %s:" % (msg['To']))

y el resto igual que siempre:


Puedes encontrar el código de este ejemplo aquí.


- Envió de texto y de un archivo adjunto.


Este quizá sea el caso más frecuente. Escribimos un texto y adjuntamos al destinatario un archivo.

En este ejemplo enviamos un pequeño texto junto con el archivo adjunto que en mi caso esta en el mismo directorio del archivo de Python y que se llama contenido.txt. (Puede ser un archivo con cualquier extensión)

Creamos el archivo de python, pej. Envio_texto_adjunto.py

from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.application import MIMEApplication
from smtplib import SMTP

Son los mismo módulos de siempre, con la salvedad de que en este caso importamos MIMEAplication.

Creamos la instancia del objeto MIMEMULTIPART.

msg = MIMEMultipart()

Creamos el texto que queremos enviar en el correo:

# Esta es la parte del email que contiene el texto:
part = MIMEText("¡Hola!, te paso un archivo interesante.")
msg.attach(part)

y esta la parte que añadirá el archivo que queremos enviar al cuerpo del mensaje:

# Esta es la parte binaria (puede ser cualquier extensión):
part = MIMEApplication(open("contenido.txt", "rb").read())
part.add_header('Content-Disposition', 'attachment', filename="contenido.txt")
msg.attach(part)

En este punto decir que se podrían seguir añadiendo más partes al mensaje, ya sea texto, imágenes, otros archivos etc.

# Configuramos los parámetros del mensaje
msg['From'] = "tu_email@outlook.com"
msg['To'] = "correo_del_destinatario@gmail.com"
msg['Subject'] = 'Esto es una prueba de envio de texto y un archivo adjunto'
password = "la contraseña de tu correo"

# Creamos una instancia del servidor y lo iniciamos
server = SMTP('smtp.outlook.com')

server.starttls()

# Nos logeamos en el servidor para poder enviar el e-mail
server.login(msg['From'], password)

# Enviar el mail (o los mails)
server.sendmail(msg['From'], msg['To'], msg.as_string())

server.quit()

print("E-mail enviado correctamente a %s:" % (msg['To']))

Puedes encontrar el código del ejemplo aquí.


- Leer E-mails en Python.


En este apartado vamos a aprender como leer e-mails con Python usando el protocolo IMAP. Este protocolo, es un protocolo estándar de internet que permite a los clientes de correo electrónico recibir e-mails del servidor que los gestiona. Además los correos no se almacenan en local sino que permanecen en el servidor.

Si lo que quieres es leer emails con Python usando algún API en vez de la librería imaplib, puedes ver este tutorial de how to use email API, el cual te puede servir de orientación.

Antes de empezar comentemos dos cuestiones.

La primera es que en este ejemplo yo estoy usando una cuenta de gmail. Si estas usando otro proveedor de correo electrónico puedes consultar una lista de los servidores de IMAP de otros proveedores aquí.

La segunda cuestión, que también nos pasaba al enviar correos con gmail, es que si al ejecutar el programa de Python te da un error de credenciales entonces debes tener activada la opción de permitir aplicaciones poco seguras en tu cuenta de gmail. (mi recomendación es que tengas una segunda cuenta de gmail para poder trastear con estas cosas de forma segura.)

Solo vamos a ver  un ejemplo simple de como leer un correo de texto_plano que esté la bandeja de entrada:

>>> # Importamos la librerías necesarias.
>>> import imaplib
>>> import email
>>> from email.header import decode_header

>>> # nuestras credenciales
>>> usuario='tu_usuario@gmail.com'
>>> password='tu_contraseña_secreta'
>>> servidor = 'imap.gmail.com' # tu usa la del del servidor que tengas

>>> # Creamos una clase IMAP4 con SSL
>>> imap = imaplib.IMAP4_SSL(servidor)

>>> # nos logueamos en el servidor
>>> imap.login(usuario, password)
('OK', [b'usuario@gmail.com authenticated (Success)'])


Si todo ha ido bien , te habrás registrado con éxito en tu cuenta y estarás listo para recibir correos.


>>> # Seleccionamos la bandeja de entrada
>>> status, mensajes = imap.select() # equivale a imap.select('INBOX')
>>> status
'OK'
>>> mensajes
[b'2']

En el ejemplo he usado el método imap.select(), el cual nos permite seleccionar un mailbox (inbox, spam,...). Por defecto si no ponemos nada el programa asume que nos referimos a los correos que existen en la bandeja de entrada (INBOX). Como veis nos devuelve una tupla con dos datos. El primero lo llamamos 'status' y hace referencia a si el proceso se realizó correctamente. El segundo 'mensajes' contiene el numero total de mensajes en ese directorio o mailbox en formato de lista. En mi cuenta la bandeja de entrada actualmente solo tiene dos correos. Podemos ver los mailboxes o directorios disponibles usando el método imap.list()

>>> # Esto es opcional.
>>> # Si queremos una lista con todos los emails que hay en inbox
>>> # para luego recorrerlos con un bucle for.
>>> status, correos = imap.search(None, 'ALL')
>>> status
'OK'
>>> correos
[b'1 2']

Los correos van de los más antiguos a los más modernos, de forma que el '1' es el más antiguo y '2' el mas reciente.

Para ver el texto crudo, tal cual, por ejemplo del más reciente que es el '2' usamos el método imap.fecth()

>>> # Obtener mi correo numero 2
>>> _, msg = imap.fetch('2', '(RFC822)')
Lo que nos devuelve msg es una lista muy larga pero, básicamente
con esta estructura:
[(b'2 (RFC822 {...}', b'...respuesta_1...'), b')']

Importante: el numero de correo lo pasamos como un string no como un número.

Todos estos comandos de imap siempre devuelven dos valores. Como el primero siempre es el status del proceso habrás visto que lo asigno con frecuencia al _ (guión bajo) que en Python es como asignarlo a una variable que no se va a utilizar. Por eso utilizamos print(msg[1]) que nos devuelve la parte exclusivamente del mensaje. RFC822 es una especificación para definir la estructura de un correo electrónico. 

Siguiendo con el proceso vamos a pasar el contenido del email que está en bytes a un objeto email con el que podamos trabajar. Si nos fijamos en el elemento msg, vemos que es una lista con dos elementos. El primero es una tupla y el segundo solo b')'. Por tanto lo que queremos es seleccionar ese primer elemento [0] de la lista. Pero es que dentro de esa tupla tambien hay dos elementos. El que nos interesa, el que tiene el contenido del e-mail es el segundo ([1] b'...respuesta_1...'. Como ves sus datos están en binario.

>>> # Seleccionamos el contenido 
>>> msg = msg[0][1]
>>> # Lo convertimos en objeto email.
>>> msg = email.message_from_bytes(msg)

Ahora es donde comienza lo bueno. Vamos a empezar a ver resultados. Empezamos obteniendo el asunto del correo.

>>> # Asunto
>>> decode_header(msg["Subject"])
[('segundo correo', None)]
>>> # Para que cada cosa este en su sitio
>>> asunto, encoding = decode_header(msg["Subject"])[0]
>>> asunto
'segundo correo'

Aunque se puede obtener igual que en el ejemplo de arriba, vamos a ver otra instrucción para obtener el remitente:

>>> # Remitente
>>> decode_header(msg.get('FROM'))
[(b'....nombre.....', 'utf-8', (b' <remitente@correo.com', None)]
>>> # Para que todo quede en su sitio
>>> remitente, encoding = decode_header(msg.get('FROM'))
>>> # como el remitente esta en binario para pasarlo a texto.
>>> remitente =  remitente.decode('utf-8')
' <remitente@correo.com>'


Aunque cuando envié el correo solo envié un texto-plano, gmail me lo envía como multipart.

>>> msg.is_multipart()
True

Por lo que tendremos que recorrer sus partes para ver cual es el cuerpo del mensaje:

>>> for parte in msg.walk():
...     tipo_contenido = parte.get_content_type()
...     if tipo_contenido == 'text/plain':
...         cuerpo = parte.get_payload(decode=True).decode()
... 

y por fin tenemos todos los elementos:

>>> print(f'Correo enviado por {remitente} con el asunto "{asunto}"\
...  y el siguiente mensaje:\n"{cuerpo}"')
Correo enviado por  <remitente@correo.com> con el asunto "segundo correo" y el siguiente mensaje:
"Para que haya un segundo correo"

Lo único que nos queda es cerrar la sesión y salir del servidor.

>>> imap.close()
('OK', [b'Returned to authenticated state. (Success)'])
>>> imap.logout()
('BYE', [b'LOGOUT Requested'])

Si queréis profundizar en esto hay dos enlaces que os recomiendo:






No hay comentarios:

Publicar un comentario