Last active
October 28, 2025 12:22
-
-
Save apermuy/997b579629e9a960b63be7c9eb488580 to your computer and use it in GitHub Desktop.
Envio correo con adjunto desde Python
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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