Skip to content

Instantly share code, notes, and snippets.

@apermuy
Last active October 28, 2025 12:22
Show Gist options
  • Select an option

  • Save apermuy/997b579629e9a960b63be7c9eb488580 to your computer and use it in GitHub Desktop.

Select an option

Save apermuy/997b579629e9a960b63be7c9eb488580 to your computer and use it in GitHub Desktop.
Envio correo con adjunto desde Python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Script para envío masivo de emails con archivos adjuntos personalizados
Lee datos desde un archivo CSV y envía emails personalizados con adjuntos+
Necesario crear el fichero contactos.csv en el mismo directorio de ejecución del script.
nombre,email,id,entrada
fulano,fulano@detal.es,1,1.jpg
mengano,mengano@decual,2,2.jpg
"""
import csv
import smtplib
import os
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
from datetime import datetime
class EmailMasivo:
"""Clase para gestionar el envío masivo de emails"""
def __init__(self, smtp_server, smtp_port, remitente, usuario=None, contrasena=None,
usar_tls=False, usar_ssl=False):
"""
Inicializa la configuración del servidor SMTP
Args:
smtp_server: Dirección del servidor SMTP
smtp_port: Puerto del servidor SMTP
remitente: Email del remitente
usuario: Usuario para autenticación SMTP (opcional)
contrasena: Contraseña para autenticación SMTP (opcional)
usar_tls: Usar TLS/STARTTLS (True/False)
usar_ssl: Usar SSL (True/False) - usa smtplib.SMTP_SSL
"""
self.smtp_server = smtp_server
self.smtp_port = smtp_port
self.remitente = remitente
self.usuario = usuario
self.contrasena = contrasena
self.usar_tls = usar_tls
self.usar_ssl = usar_ssl
self.log_envios = []
def leer_csv(self, ruta_csv):
"""
Lee el archivo CSV y retorna una lista de diccionarios
Args:
ruta_csv: Ruta al archivo CSV
Returns:
Lista de diccionarios con los datos del CSV
"""
contactos = []
try:
with open(ruta_csv, 'r', encoding='utf-8') as archivo:
lector = csv.DictReader(archivo)
for fila in lector:
contactos.append(fila)
print(f"✓ Se cargaron {len(contactos)} contactos desde {ruta_csv}")
return contactos
except FileNotFoundError:
print(f"✗ Error: No se encontró el archivo {ruta_csv}")
return []
except Exception as e:
print(f"✗ Error al leer el CSV: {str(e)}")
return []
def personalizar_texto(self, plantilla, datos):
"""
Personaliza un texto reemplazando los placeholders con datos del contacto
Args:
plantilla: Texto con placeholders {nombre}, {email}, {id}
datos: Diccionario con los datos del contacto
Returns:
Texto personalizado
"""
texto = plantilla
for campo, valor in datos.items():
texto = texto.replace(f"{{{campo}}}", str(valor))
return texto
def adjuntar_archivo(self, mensaje, ruta_archivo):
"""
Adjunta un archivo al mensaje de email
Args:
mensaje: Objeto MIMEMultipart
ruta_archivo: Ruta al archivo a adjuntar
Returns:
True si se adjuntó correctamente, False en caso contrario
"""
try:
if not os.path.exists(ruta_archivo):
print(f" ⚠ Advertencia: No se encontró el archivo {ruta_archivo}")
return False
# Abrir el archivo en modo binario
with open(ruta_archivo, 'rb') as archivo:
parte = MIMEBase('application', 'octet-stream')
parte.set_payload(archivo.read())
# Codificar el archivo en base64
encoders.encode_base64(parte)
# Agregar el header con el nombre del archivo
nombre_archivo = os.path.basename(ruta_archivo)
parte.add_header(
'Content-Disposition',
f'attachment; filename= {nombre_archivo}'
)
mensaje.attach(parte)
return True
except Exception as e:
print(f" ✗ Error al adjuntar archivo {ruta_archivo}: {str(e)}")
return False
def enviar_email(self, destinatario, asunto, cuerpo, archivo_adjunto=None):
"""
Envía un email individual
Args:
destinatario: Email del destinatario
asunto: Asunto del email
cuerpo: Cuerpo del email (puede ser HTML)
archivo_adjunto: Ruta al archivo a adjuntar (opcional)
Returns:
True si se envió correctamente, False en caso contrario
"""
try:
# Crear el mensaje
mensaje = MIMEMultipart()
mensaje['From'] = self.remitente
mensaje['To'] = destinatario
mensaje['Subject'] = asunto
# Agregar el cuerpo del mensaje
mensaje.attach(MIMEText(cuerpo, 'html', 'utf-8'))
# Adjuntar archivo si se especifica
if archivo_adjunto:
if not self.adjuntar_archivo(mensaje, archivo_adjunto):
print(f" ⚠ Email enviado sin adjunto")
# Conectar al servidor SMTP y enviar
if self.usar_ssl:
# Usar SMTP_SSL (conexión SSL desde el inicio)
servidor = smtplib.SMTP_SSL(self.smtp_server, self.smtp_port)
else:
# Usar SMTP normal
servidor = smtplib.SMTP(self.smtp_server, self.smtp_port)
# Si se especifica TLS, usar STARTTLS
if self.usar_tls:
servidor.starttls()
try:
# Autenticar si se proporcionaron credenciales
if self.usuario and self.contrasena:
servidor.login(self.usuario, self.contrasena)
# Enviar el mensaje
servidor.send_message(mensaje)
finally:
servidor.quit()
return True
except Exception as e:
print(f" ✗ Error al enviar email: {str(e)}")
return False
def enviar_masivo(self, ruta_csv, plantilla_asunto, plantilla_cuerpo):
"""
Envía emails masivos a todos los contactos del CSV
Args:
ruta_csv: Ruta al archivo CSV
plantilla_asunto: Plantilla del asunto con placeholders
plantilla_cuerpo: Plantilla del cuerpo con placeholders
"""
contactos = self.leer_csv(ruta_csv)
if not contactos:
print("No hay contactos para procesar.")
return
print(f"\n{'='*60}")
print(f"Iniciando envío masivo de emails")
print(f"Fecha: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"{'='*60}\n")
exitosos = 0
fallidos = 0
for i, contacto in enumerate(contactos, 1):
print(f"[{i}/{len(contactos)}] Procesando: {contacto.get('nombre', 'N/A')} ({contacto.get('email', 'N/A')})")
# Personalizar asunto y cuerpo
asunto = self.personalizar_texto(plantilla_asunto, contacto)
cuerpo = self.personalizar_texto(plantilla_cuerpo, contacto)
# Obtener la ruta del archivo adjunto
archivo_adjunto = contacto.get('entrada', '').strip()
if not archivo_adjunto:
print(" ⚠ Sin archivo adjunto especificado")
archivo_adjunto = None
# Enviar el email
if self.enviar_email(contacto['email'], asunto, cuerpo, archivo_adjunto):
print(f" ✓ Email enviado exitosamente")
exitosos += 1
self.log_envios.append({
'email': contacto['email'],
'nombre': contacto.get('nombre', 'N/A'),
'estado': 'Exitoso',
'fecha': datetime.now()
})
else:
print(f" ✗ Falló el envío")
fallidos += 1
self.log_envios.append({
'email': contacto['email'],
'nombre': contacto.get('nombre', 'N/A'),
'estado': 'Fallido',
'fecha': datetime.now()
})
print()
# Resumen final
print(f"{'='*60}")
print(f"RESUMEN DEL ENVÍO")
print(f"{'='*60}")
print(f"Total de contactos: {len(contactos)}")
print(f"Exitosos: {exitosos}")
print(f"Fallidos: {fallidos}")
print(f"{'='*60}\n")
def guardar_log(self, ruta_log='log_envios.txt'):
"""
Guarda un log de los envíos realizados
Args:
ruta_log: Ruta donde guardar el archivo de log
"""
try:
with open(ruta_log, 'w', encoding='utf-8') as archivo:
archivo.write(f"Log de envíos - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
archivo.write("="*80 + "\n\n")
for envio in self.log_envios:
archivo.write(f"Email: {envio['email']}\n")
archivo.write(f"Nombre: {envio['nombre']}\n")
archivo.write(f"Estado: {envio['estado']}\n")
archivo.write(f"Fecha: {envio['fecha'].strftime('%Y-%m-%d %H:%M:%S')}\n")
archivo.write("-"*80 + "\n")
print(f"✓ Log guardado en: {ruta_log}")
except Exception as e:
print(f"✗ Error al guardar el log: {str(e)}")
def main():
"""Función principal del script"""
# ==================== CONFIGURACIÓN ====================
# Configuración del servidor SMTP
SMTP_SERVER = '1.2.3.4'
SMTP_PORT = 25
REMITENTE = 'some@example.com'
# === Autenticación (dejar en None si no se requiere) ===
USUARIO = None # Ej: 'usuario@example.com' o 'usuario'
CONTRASENA = None # Ej: 'tu_contraseña_segura'
# === Seguridad TLS/SSL ===
# Solo activar UNA de las siguientes opciones:
USAR_TLS = False # True para STARTTLS (puerto 587 típicamente)
USAR_SSL = False # True para SSL directo (puerto 465 típicamente)
# EJEMPLOS DE CONFIGURACIÓN COMÚN:
#
# Gmail:
# SMTP_SERVER = 'smtp.gmail.com'
# SMTP_PORT = 587
# USUARIO = 'tu_email@gmail.com'
# CONTRASENA = 'tu_contraseña_de_aplicación'
# USAR_TLS = True
# USAR_SSL = False
#
# Outlook/Hotmail:
# SMTP_SERVER = 'smtp-mail.outlook.com'
# SMTP_PORT = 587
# USUARIO = 'tu_email@outlook.com'
# CONTRASENA = 'tu_contraseña'
# USAR_TLS = True
# USAR_SSL = False
#
# Office 365:
# SMTP_SERVER = 'smtp.office365.com'
# SMTP_PORT = 587
# USUARIO = 'tu_email@tudominio.com'
# CONTRASENA = 'tu_contraseña'
# USAR_TLS = True
# USAR_SSL = False
#
# Servidor local sin autenticación:
# SMTP_SERVER = 'localhost'
# SMTP_PORT = 25
# USUARIO = None
# CONTRASENA = None
# USAR_TLS = False
# USAR_SSL = False
# Ruta al archivo CSV
RUTA_CSV = 'contactos.csv'
# Plantillas personalizables (puedes usar {nombre}, {email}, {id})
PLANTILLA_ASUNTO = 'Hola {nombre}, aquí está tu documento'
PLANTILLA_CUERPO = """
<html>
<body>
<h2>Hola {nombre},</h2>
<p>Esperamos que te encuentres bien.</p>
<p>Adjunto encontrarás el documento que solicitaste.</p>
<p>Tu ID de referencia es: <strong>{id}</strong></p>
<br>
<p>Si tienes alguna pregunta, no dudes en contactarnos respondiendo a este email: {email}</p>
<br>
<p>Saludos cordiales,</p>
<p><strong>El Equipo A</strong></p>
</body>
</html>
"""
# =======================================================
print("\n" + "="*60)
print("SISTEMA DE ENVÍO MASIVO DE EMAILS")
print("="*60 + "\n")
# Crear instancia de EmailMasivo
email_masivo = EmailMasivo(
smtp_server=SMTP_SERVER,
smtp_port=SMTP_PORT,
remitente=REMITENTE,
usuario=USUARIO,
contrasena=CONTRASENA,
usar_tls=USAR_TLS,
usar_ssl=USAR_SSL
)
# Realizar el envío masivo
email_masivo.enviar_masivo(RUTA_CSV, PLANTILLA_ASUNTO, PLANTILLA_CUERPO)
# Guardar log
email_masivo.guardar_log()
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment