Programa desarrollado en Python 3 para el envío de mensajes con contenido HMTL o Texto plano y con soporte para SSL/TLS o STARTTLS y archivos adjuntos.
Tabla de contenidos
Introducción
Programa para realizar envíos de correo con contenido en formato HTML o Texto plano, a partir de un archivo con extensión .html o .txt respectivamente.
Dispone de soporte para SSL/TLS y STARTTLS.
Posibilidad de enviar a uno o varios destinatarios, incluidos en CC, y con cambio de identidad, si el proveedor del servicio de correo lo permite, aunque no recomendamos el uso de esta práctica.
También tenemos la opción de adjuntar uno o varios archivos en el mismo envío.
Requisitos
El programa está desarrollado para ser usado con Python 3.x.
Instalación
No se requiere la instalación de módulos adicionales, ya que se encuentran incluidos por defecto en la versión del lenguaje en el que se ha desarrollado. Por lo tanto, tampoco será necesario ejecutar en un entorno virtual de Python.
Para clonar el repositorio nos ubicamos en el directorio en donde queramos instalar el programa y usamos el siguiente comando para clonarlo:
git clone https://sergiobr@bitbucket.org/sergiobr/smtp-contenido-html-o-texto-plano-desde-python.git
Y ya estaría listo para su uso en el directorio en donde lo hayamos clonado.
También podemos crear el fichero con el contenido del programa, pues fue desarrollado en un mismo archivo, aunque te recomiendo hacer el clonado por si se suben actualizaciones a BitBucket. Contenido:
import os
from os.path import basename
# Options
from optparse import OptionParser
# Email
from email.utils import formatdate
#from email.mime.image import MIMEImage
import smtplib
from email.mime.application import MIMEApplication
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# VARIABLES
################
# Email server.
emailHost = ''
# Int.
emailPort = 25
# True/False.
emailTLS = False
emailSSL = False
# Email credentials.
emailUser = ''
emailPwd = ''
# Email subject.
emailSubject = ''
# Email From info.
emailFrom = ''
# Email To and CC.
emailTo = ''
emailToCc = ''
# Attachment/s.
emailAttachment = ''
# Replay header.
emailReplyTo = ''
# CLASSES
#######################
# Options parser
def programParser():
try:
# Description and version variables.
usage = "python %prog [options] bodyContentFile.[html|txt]"
description = "Program to send email with HTML or Plain text content, with also attachments.\n\nConfigure the variables section to connect to your email server, or use the options to change any of these.\n\nSpecify the body content in a .html or .txt file, depending on the format you choose.\n\nNever use single quotes ('') in each option value. If you not add white spaces quotes are not neccesary, but if you use it only double quotes (\"\") are allowed."
version = '%prog: version 1.0'
# Parser init.
parser = OptionParser(usage,version=version,description=description,add_help_option=True)
# Parser options.
parser.add_option('-H', '--host', dest = 'host', help = 'SMTP server.')
parser.add_option('-p', '--port', dest = 'port', help = 'SMTP port.')
parser.add_option('--tls', action = 'store_true', help = 'TLS security on.')
parser.add_option('--ssl', action = 'store_true', help = 'SSL security on.')
parser.add_option('--nosecurity', action = 'store_true', help = 'No TLS or SSL security.')
parser.add_option('-u', '--username', dest = 'username', help = 'Username login.')
parser.add_option('-P', '--password', dest = 'password', help = 'Password login.')
parser.add_option('-s', '--subject', dest = 'subject', help = 'Email subject, between double quotes (\"\").')
parser.add_option('-f', '--from', dest = 'emailFrom', help = 'Email from. Different than Username could be considered as Email Spoofing. If it is not defined it will be the same than the user name.')
parser.add_option('-t', '--to', dest = 'emailTo', help = 'Email destination. More than one separated by comas (,), with no white spaces.')
parser.add_option('-c', '--carboncopy', dest = 'emailToCc', help = 'Email destination copy. More than one separated by comas (,), with no white spaces.')
parser.add_option('-a', '--attachment', dest = 'emailAttachment', help = 'Attach a file or files, separated by comas (,) and between double quotes (\"\").')
parser.add_option('-r', '--replyto', dest = 'emailReplyTo', help = 'Email to reply. If it is not defined it will be the email from.')
return parser
except Exception as e:
raise ValueError('Function - creating program parser: %s' % str(e))
# Check parser to change file variables.
def checkParserOptionsArguments(options,args):
try:
# ARGUMENTS, only 1 is available. No arguments or more than one show an error.
if len(args) == 0:
raise ValueError('No argument has been indicated.')
elif len(args) > 1 or len(args) < 0:
raise ValueError('Only one argument is allowed.')
else:
# Check the argument extension. Only .html or .txt are available.
extension = args[0].split('.')
fileExt = extension[1].lstrip('\'').rstrip('\'')
# Other extensions than .html or .txt extension show an error.
if fileExt != 'html' and fileExt != 'txt':
raise ValueError('Only files with extensions .html or .txt are allowed.')
else:
# Identify email type by the file extension (HTML or Plain text).
global emailType
if fileExt == 'html':
emailType = 'html'
elif fileExt == 'txt':
emailType = 'plain'
# Parser options added.
# Email Host.
if options.host:
global emailHost
emailHost = str(options.host.lstrip('\'').rstrip('\''))
# Email Port.
if options.port:
global emailPort
emailPort = int(options.port.lstrip('\'').rstrip('\''))
# Security, only one or none allowed.
if options.tls and options.ssl:
raise ValueError('Only one option is available, --ssl or --tls, or nothing.')
elif options.nosecurity:
global emailSSL
global emailTLS
emailTLS = False
emailSSL = False
else:
if options.tls:
emailTLS = True
emailSSL = False
if options.ssl:
emailSSL = True
emailTLS = False
# Email Username
if options.username:
global emailUser
emailUser = str(options.username.lstrip('\'').rstrip('\''))
# Email password
if options.password:
global emailPwd
emailPwd = str(options.password.lstrip('\'').rstrip('\''))
# Email subject.
if options.subject:
global emailSubject
emailSubject = str(options.subject.lstrip('\'').rstrip('\''))
# Email from.
if options.emailFrom:
global emailFrom
emailFrom = str(options.emailFrom)
# Email/s to.
if options.emailTo:
global emailTo
emailTo = str(options.emailTo.lstrip('\'').rstrip('\''))
# Email/s in Carbon Copy.
if options.emailToCc:
global emailToCc
emailToCc = str(options.emailToCc.lstrip('\'').rstrip('\''))
# Email attachment/s.
if options.emailAttachment:
global emailAttachment
emailAttachment = str(options.emailAttachment.lstrip('\'').rstrip('\''))
# Email reply header.
if options.emailReplyTo:
global emailReplyTo
emailReplyTo = str(options.emailReplyTo.lstrip('\'').rstrip('\''))
except Exception as e:
raise ValueError('Function - checking parser variables: %s' % str(e))
# Email Class
class emailClass():
# Constructor with email config variables.
def __init__(self,emailHost,emailPort,emailTLS,emailSSL,emailUser,emailPwd,emailSubject,emailFrom,emailTo,emailToCc,emailReplyTo):
self.emailHost = emailHost
self.emailPort = emailPort
self.emailTLS = emailTLS
self.emailSSL = emailSSL
self.emailUser = emailUser
self.emailPwd = emailPwd
self.emailSubject = emailSubject
self.emailFrom = emailFrom
if not self.emailFrom:
self.emailFrom = self.emailUser
elif not '@' in self.emailFrom:
self.emailFrom = self.emailFrom + " <" + self.emailUser + ">"
self.emailTo = emailTo
self.emailToCc = emailToCc
self.emailReplyTo = emailReplyTo
if not self.emailReplyTo:
self.emailReplyTo = self.emailFrom
self.mailRcpt = self.emailTo.split(',') + self.emailToCc.split(',')
if self.emailTLS and self.emailSSL:
raise ValueError('Check variables for SSL and TLS in the program, only one or nothing are availables.')
# Send the email from config variables (from file or command options).
def emailSend(self,type,bodyContent,attach=None):
try:
# Type (HTML or Plain text).
if type == 'html':
msg = MIMEMultipart('related')
elif type == 'plain':
msg = MIMEMultipart()
else:
raise ValueError('Class - Error declaring email type (html or plain).')
# Message info.
msg['Subject'] = self.emailSubject
msg['From'] = self.emailFrom
msg['To'] = self.emailTo
msg['Cc'] = self.emailToCc
msg["Date"] = formatdate(localtime=True)
msg["Reply-To"] = self.emailReplyTo
# Body content.
with open(bodyContent) as f:
body = f.read()
if type == 'html':
part2 = MIMEText(body,'html')
elif type == 'plain':
part2 = MIMEText(body,'plain')
else:
raise ValueError('Class - Error declaring email type (html or plain).')
msg.attach(part2)
# Attach file/s, it it has been indicated.
if attach:
for f in attach.split(','):
with open(f, "rb") as file:
part = MIMEApplication(file.read(), Name=basename(f))
part['Content-Disposition'] = 'attachment; filemane=%s' % basename(f)
msg.attach(part)
# Create message.
textBody = msg.as_string()
# Configure security, if it has been configured.
if self.emailSSL:
connection = smtplib.SMTP_SSL(self.emailHost,self.emailPort)
connection.ehlo()
else:
connection = smtplib.SMTP(self.emailHost,self.emailPort)
connection.ehlo()
if self.emailTLS:
connection.starttls()
# Email credentials, if it has been configured.
if self.emailUser:
connection.login(self.emailUser,self.emailPwd)
# Send the email.
connection.sendmail(self.emailFrom,self.mailRcpt,textBody)
# Close connection.
connection.quit()
except Exception as e:
raise ValueError('Class error: %s' % (str(e)))
# PROGRAM
###########
try:
# Parse argument and options, if exists.
parser = programParser()
(options, args) = parser.parse_args()
# Check parser options and argument.
checkParserOptionsArguments(options,args)
email = emailClass(emailHost,emailPort,emailTLS,emailSSL,emailUser,emailPwd,emailSubject,emailFrom,emailTo,emailToCc,emailReplyTo)
# html or plain / html body file or plain text body, and the attachment if is required.
email.emailSend(emailType,args[0].lstrip('\'').rstrip('\''),emailAttachment)
# If no error is shown, print ok.
print('Email sent!!!')
except Exception as e:
print('ERROR - %s' % (str(e)))
finally:
print('\nPowered by https://blog.tiraquelibras.com')
Configuración
Dentro del ejecutable
Dentro del archivo envio.py tenemos al comienzo la sección VARIABLES en donde podemos indicar los datos de acceso al buzón de correo para realizar el envío, incluido/s el/los destinatario/s del mensaje.
# Email server.
emailHost = ''
# Int.
emailPort = 25
# True/False.
emailTLS = False
emailSSL = False
# Email credentials.
emailUser = ''
emailPwd = ''
# Email subject.
emailSubject = ''
# Email From info.
emailFrom = ''
# Email To and CC.
emailTo = ''
emailToCc = ''
# Attachment/s.
emailAttachment = ''
# Replay header.
emailReplyTo = ''
En esta sección configuramos:
- emailHost Servidor de email.
- emailPort Puerto del servidor de email.
- emailSSL/emailTLS Seguridad SSL/TLS o STARTTLS, si tuviera alguno.
- emailUser/emailPwd Credenciales del buzón para realizar el envío.
- emailSubject Asunto del mensaje.
- emailFrom Información del origen o From.
- emailTo/emailCc Destinatarios y destinatarios en copia.
- emailAttachment Adjuntos, si los tuviera.
- emailReplyTo Cabecera de respuesta.
Usando las opciones del programa (prioritario)
También podemos indicar estos parámetros en las opciones del programa, las cuales tienen prioridad sobre lo que se configure en el archivo, comentado justo encima de estas líneas, en la sección Dentro del ejecutable.
Estas serían las opciones:
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-H HOST, --host=HOST SMTP server.
-p PORT, --port=PORT SMTP port.
--tls TLS security on.
--ssl SSL security on.
--nosecurity No TLS or SSL security.
-u USERNAME, --username=USERNAME
Username login.
-P PASSWORD, --password=PASSWORD
Password login.
-s SUBJECT, --subject=SUBJECT
Email subject, between double quotes ("").
-f EMAILFROM, --from=EMAILFROM
Email from. Different than Username could be
considered as Email Spoofing. If it is not defined it
will be the same than the user name.
-t EMAILTO, --to=EMAILTO
Email destination. More than one separated by comas
(,), with no white spaces.
-c EMAILTOCC, --carboncopy=EMAILTOCC
Email destination copy. More than one separated by
comas (,), with no white spaces.
-a EMAILATTACHMENT, --attachment=EMAILATTACHMENT
Attach a file or files, separated by comas (,) and
between double quotes ("").
-r EMAILREPLYTO, --replyto=EMAILREPLYTO
Email to reply. If it is not defined it will be the
email from.
El listado de opciones mostradas son las siguientes:
Opción | Variante | Descripción |
---|---|---|
–version | Muestra la versión del programa. | |
-h | –help | Muestra la ayuda del programa. |
-H <host> | –host=<host> | Servidor de correo para realizar el envío. |
-p <port> | –port=<port> | Puerto del servidor de correo. |
–tls | Seguridad STARTTLS. | |
–ssl | Seguridad SSL. | |
–nosecurity | Anular cualquier tipo de seguridad. | |
-u <username> | –username=<username> | Login de usuario. |
-P <passwd> | –password=<passwd> | Contraseña de usuario. |
-s <subject> | –subject=<subject> | Asunto del mensaje. |
-f <from> | –from=<from> | Información del From del mensaje. |
-t <to> | –to=<to> | Destinatario/s, separados por comas (,). |
-c <cc> | –carboncopy=<cc> | Destinatario/s en copia, separados por comas(,). |
-a <file/s> | –attachment=<file/s> | Archivo/s adjunto/s, separados por comas (,). |
-r <reply-to> | –replyto=<reply-to> | Cabecera de respuesta. Si no se indica coincidirá con la dirección de From. |
Ejecución
Para ejecutar el programa utilizamos el siguiente comando:
python3 envio.py <argumento> <opciones>
Siendo en nuestro caso:
- python3 la versión de Python instalada, en nuestro caso python3.
- envio.py es el archivo que contiene nuestro código del programa.
- <argumento> archivo.html o archivo.txt con el contenido del email.
- <opciones> las opciones que veremos más adelante en la ayuda.
Por ejemplo:
python3 envio.py archivo.html
Para consultar las opciones del programa disponemos de una ayuda contextual agregando la opción -h o –help. Recordar que estas tienen prioridad sobre las variables configuradas en el mismo ejecutable, comentado en la sección anterior de Configuración:
python3 envio.py archivo.html --help
La explicación de cada opción la puedes ver en la sección anterior.
Indicar que las opciones en formato string no deben de contener espacios en blanco, o si los contienen como por ejemplo el asunto se deben de utilizar comillas dobles (» «), nunca usar comillas simples (‘ ‘). El programa está diseñado para sustituirlas, pero puede que algún caso no se hubiera contemplado, dando un error como resultado final. Por ejemplo, si queremos enviar a más de un destinatario podemos indicar las direcciones sin comillas dobles y sin espacios en blanco:
email1@example.org,email2@example.org
O con comilla dobles, por ejemplo si usamos espacios como en el asunto:
«email1@example.org,email2@example.org«
Para mostrar la versión del programa ejecutamos el comando:
python envio.py --version
Creamos un archivo prueba.html con el siguiente contenido:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>Email de prueba</h1>
<hr>
<p>Esto es un email de prueba.</p>
<br>
<p>Saludos</p>
</body>
</html>
Y lo enviamos ejecutando el comando de la siguiente forma, sin usar opciones, únicamente con las variables configuradas en el ejecutable:
python envio.py prueba.html
Siendo el resultado el siguiente:
$ python envio.py prueba.html
Email sent!!!
Powered by https://blog.tiraquelibras.com
Y en el destino se vería de la siguiente forma:
Lo mismo con un archivo de texto con extensión .txt
Enlaces de interés
Acceso al repositorio en BitBucket.