Cómo Enviar correo electrónico a través de API en Python (Tutorial Rápido)
Omite la información de configuración de SMTP. Esta guía te muestra cómo usar el API de correo electrónico unificado de Unipile para enviar correo electrónico en Python - con ejemplos de copiar y pegar para Gmail, Outlook e IMAP utilizando la función solicita biblioteca.
import solicitudes, os
CLAVE_API = os.environ['UNIPILE_API_KEY']
DSN = os.environ['UNIPILE_DSN']
ID_CUENTA = os.environ['UNIPILE_CUENTA_ID']
response = requests.Correo electrónico:(
f'{DSN}/api/v1/correos',
encabezados={'X-API-KEY': CLAVE_API},
datos={
'id_cuenta': ID_CUENTA,
'a': '[{"display_name":"Alice","identifier":"alice@acme.com"}]',
'asunto': 'Hola desde Python',
'cuerpo': '¡Enviado vía Unipile!
'
}
)
print(respuesta.json())Ejemplo de Python de 5 líneas
Si ya sabes qué es una API de envío de correo electrónico y solo quiero el código Python de la API de correo electrónico que funcione, aquí está. El tutorial completo sigue a continuación.
pip install requests python-dotenvUNIPILE_DSN, UNIPILE_API_KEYy UNIPILE_ID_DE_CUENTA a tu .env archivo./api/v1/emailsaccount_id, a, temay cuerpo. Hecho.import solicitudes, os
from dotenv import cargar_dotenv
cargar_dotenv()
CLAVE_API = os.environ['UNIPILE_API_KEY']
DSN = os.environ['UNIPILE_DSN']
ID_CUENTA = os.environ['UNIPILE_CUENTA_ID']
resp = requests.Correo electrónico:(
f'{DSN}/api/v1/correos',
encabezados={'X-API-KEY': CLAVE_API},
datos={
'id_cuenta': ID_CUENTA,
'a': '[{"display_name":"Alice","identifier":"alice@acme.com"}]',
'asunto': 'Hola desde Python',
'cuerpo': '¡Enviado vía Unipile!
'
}
)
print(resp.json()) # {'tracking_id': 'msg_...'}Requisitos previos y configuración
Antes de poder usar el flujo de trabajo de Python de la API de correo electrónico en producción, necesita cuatro cosas: Python 3.9+, la solicita biblioteca, una clave de API con DSN y una cuenta de correo electrónico vinculada.
| tipos de unión y características de la biblioteca estándar de Python 3.9+. Se recomienda Python 3.11 LTS para producción. Verifica tu versión con python --version.api4.unipile.com:13444. Ambos son necesarios en cada encabezado de solicitud.python -m venv .venv && source .venv/bin/activate. Nunca instales paquetes en el Python del sistema, esto es especialmente importante para el manejo de credenciales.pip install requests python-dotenv
# Opcional: soporte asíncrono
pip install aiohttp httpx
# Opcional: lógica de reintento
pip install tenacitypipenv install requests python-dotenv tenacitypoetry add requests python-dotenv tenacity# Credenciales de Unipile - nunca confirmes este archivo
UNIPILE_DSN=https://api4.unipile.com:13444
UNIPILE_API_KEY=tu_token_de_acceso_aqui
# La ID de la cuenta de correo electrónico vinculada
UNIPILE_ID_DE_CUENTA=acc_xxxxxxxxxxxxxxxxAñadir .env a tu .gitignore. Cargar con python-dotenv vía cargar_dotenv() en la parte superior de tu script. En producción, prefiere variables de entorno reales inyectadas por tu plataforma de despliegue (Heroku, Railway, Docker Compose).
Conectando tu primera cuenta de correo electrónico
Antes de poder enviar, necesitas vincular una cuenta de correo electrónico a Unipile. Este es un paso único por cuenta. Ver el completo guía de integración de API de correo electrónico unificada para más información sobre flujos de cuentas múltiples.
Unipile utiliza un asistente de autenticación alojado: tu script de Python genera un enlace de autenticación, el usuario hace clic en él y completa OAuth en el navegador, luego Unipile llama a tu webhook con el nuevo account_id. No se almacenan credenciales SMTP en tu código para Gmail o Outlook.
import solicitudes, os
from dotenv import cargar_dotenv
cargar_dotenv()
CLAVE_API = os.environ['UNIPILE_API_KEY']
DSN = os.environ['UNIPILE_DSN']
# Paso 1: crear un enlace de autenticación alojado para OAuth de Gmail
resp = requests.Correo electrónico:(
f'{DSN}/api/v1/hosted/accounts/link',
encabezados={'X-API-KEY': CLAVE_API},
datos={
'tipo': 'GOOGLE',
'nombre': 'Alice Gmail',
'url_exito': 'https://yourapp.com/oauth/success',
'url_fallo': 'https://yourapp.com/oauth/fallo'
}
)
# Paso 2: envía esta URL a tu usuario
auth_url = resp.json()['url']
print(Dirigir al usuario a: {auth_url}')
# Paso 3: Unipile publica POSTs de {account_id} en tu webhook después de OAuth
# Ver /gmail-api-send-email-a-comprehensive-guide-for-developers/ para detalles de Gmailimport solicitudes, os
from dotenv import cargar_dotenv
cargar_dotenv()
# Outlook OAuth - cubre Outlook personal + Microsoft 365
# Ver /microsoft-graph-api-email-integration-guide/
resp = requests.Correo electrónico:(
f'{os.environ["UNIPILE_DSN"]}/api/v1/hosted/accounts/link',
encabezados={'X-API-KEY'os.environ['UNIPILE_API_KEY']},
datos={
'tipo': 'MICROSOFT',
'nombre': 'Bob Outlook',
'url_exito': 'https://yourapp.com/oauth/success',
'url_fallo': 'https://yourapp.com/oauth/fallo'
}
)
print(resp.json()['url'])import requests, os, json
# IMAP: pase las credenciales SMTP/IMAP directamente (no se necesita redirección OAuth)
# Ver /the-developers-guide-to-imap-api-solution/ para obtener detalles completos de la API de IMAP
resp = requests.Correo electrónico:(
f'{os.environ["UNIPILE_DSN"]}/api/v1/cuentas',
encabezados={'X-API-KEY'os.environ['UNIPILE_API_KEY']},
json={
'proveedor': 'IMAP',
'nombre de usuario': 'alice@company.com',
'contraseña': 'contraseña_de_aplicación_aquí',
'imap_host': 'imap.company.com',
'smtp_host': 'smtp.company.com'
}
)
account_id = resp.json()['id_cuenta']
print(Cuenta vinculada: {account_id}')Enviando tu primer correo electrónico desde Python
El punto de conexión de envío acepta multipart/form-data. Usa datos= no jsonen requests.post(). El a, ccy con copia oculta los campos son cadenas codificadas en JSON dentro de los datos del formulario.
import requests, os, json
solicitudes.Correo electrónico:(
f'{os.environ["UNIPILE_DSN"]}/api/v1/correos',
encabezados={'X-API-KEY'os.environ['UNIPILE_API_KEY']},
datos={
'id_cuenta'os.environ['UNIPILE_CUENTA_ID'],
'a': json.vertidos([{'nombre_en_pantalla': 'Alicia', 'identificador': 'alice@acme.com'}]),
'asunto': 'Actualización rápida',
'cuerpo': 'Hola Alice, solo quería contactarte.'
}
)cuerpo El campo acepta texto plano y HTML. Usa etiquetas para formato HTML.import requests, os, json
response = requests.Correo electrónico:(
f'{os.environ["UNIPILE_DSN"]}/api/v1/correos',
encabezados={'X-API-KEY'os.environ['UNIPILE_API_KEY']},
datos={
'id_cuenta'os.environ['UNIPILE_CUENTA_ID'],
'a'json.vertidos([{'identificador': 'alice@acme.com'}]),
'cc'json.vertidos([{'identificador': 'manager@acme.com'}]),
'CCO': json.vertidos([{'identificador': 'crm@yourapp.com'}]),
'asunto': 'Tu factura está lista',
'cuerpo': 'Factura #1042
Por favor, encuentre su factura adjunta.
'
}
)
# 202 Aceptado = en cola para entrega
print(código_estado.respuesta, respuesta.json())import requests, os, json
def enviar_correopara_correo: cadena, asunto: cadena, cuerpo: cadena) -> diccionario:
"""Enviar correo electrónico a través del wrapper de Python de la API de correo electrónico de Unipile."""
response = requests.Correo electrónico:(
f'{os.environ["UNIPILE_DSN"]}/api/v1/correos',
encabezados={'X-API-KEY'os.environ['UNIPILE_API_KEY']},
datos={
'id_cuenta'os.environ['UNIPILE_CUENTA_ID'],
'a'json.vertidos([{'identificador': para_correo}]),
'asunto': asunto,
'cuerpo'cuerpo,
},
tiempo de espera=30
)
respuesta.lanzar_para_estado() # genera HTTPError en 4xx/5xx
return respuesta.json() # {'tracking_id': 'msg_...'}tiempo de espera=30 para evitar que se cuelgue para siempre en problemas de red. Usar raise_for_status() para propagar errores HTTP como excepciones de Python.Obtenga su clave de API, vincule una cuenta de Gmail o Outlook en minutos y ejecute los ejemplos de Python de esta guía contra buzones reales.
Enviar archivos adjuntos en Python
Los archivos adjuntos se envían como parte de los datos del formulario multipart usando Python archivos= parámetro. Abre el archivo en modo binario ('rb') - bytes, no cadenas.
import requests, os, json
# Adjunto de un solo archivo
con abrir('factura.pdf', 'rb') como f:
resp = requests.Correo electrónico:(
f'{os.environ["UNIPILE_DSN"]}/api/v1/correos',
encabezados={'X-API-KEY'os.environ['UNIPILE_API_KEY']},
datos={
'id_cuenta'os.environ['UNIPILE_CUENTA_ID'],
'a': json.vertidos([{'identificador': 'client@example.com'}]),
'asunto': 'Factura adjunta',
'cuerpo': 'Por favor, vea la factura adjunta.
'
},
archivos={'adjuntos': ('factura.pdf', f, 'application/pdf')}
)
# Múltiples adjuntos: pasar una lista de tuplas
# archivos=[('adjuntos', ('a.pdf', f1, 'application/pdf')),
# ('adjuntos', ('b.png', f2, 'image/png'))]abrir('archivo.pdf', 'rb'), no 'r'. Pasar un objeto de archivo de texto a archivos= plantea Error de tipo. Esta es una peculiaridad común específica de Python al migrar desde smtplib.archivos=cada tupla es ('adjuntos', (nombre_archivo, objeto_archivo, tipo_contenido)). Requests maneja el límite multipart automáticamente.BytesIO objeto directamente desde io import BytesIO; buf = BytesIO(pdf_bytes) entonces ('informe.pdf', buf, 'application/pdf').Respuestas, Hilos y Seguimiento
Para enviar un correo electrónico en nombre de un usuario, concurrencia, y seguimiento de entrega basado en webhooks, estos son los patrones de Python que necesitas.
01Enhebrar con in_reply_to
Para responder dentro de un hilo existente, pase la en_respuesta_a campo con el seguimiento_id del correo electrónico al que desea responder. Unipile se encarga del Referencias y En respuesta a encabezados automáticamente.
solicitudes.Correo electrónico:(
f'{DSN}/api/v1/correos',
encabezados={'X-API-KEY': CLAVE_API},
datos={
'id_cuenta': ID_CUENTA,
'a': json.vertidos([{'identificador': 'alice@acme.com'}]),
'asunto': 'Re: Tu pregunta',
'cuerpo': 'Dando seguimiento a tu mensaje.
',
'en_respuesta_a': 'ID de seguimiento original'
}
)02Webhooks en Python (ejemplo con Flask)
Registre una URL de webhook en su panel de Unipile para recibir eventos de entrega (enviado, rebotado, abierto). Aquí hay un receptor básico de Flask:
from frasco import Flask, request, jsonify
import registro
apartamento = Matraz(__nombre__)
registro.basicConfig(nivel=logging.INFORMACIÓN)
@app.ruta('/webhook/email', métodos=[POST])
def webhook_de_correo_electronico():
evento = solicitud.get_json()
tipo_evento = evento.consiga('tipo')
tracking_id = evento.consiga('id_de_seguimiento')
registro.información(f'Correo electrónico del evento: {event_type} para {tracking_id}')
return convertir a JSONok=Verdadero), 20003Claves de idempotencia
Para evitar envíos duplicados en reintentos de red, pasa un único Clave-Idempotencia header. Si la misma clave se envía dos veces, Unipile devuelve la respuesta original sin enviar un segundo correo electrónico.
import uuid, solicitudes, os, json
clave = cadenauuid.uuid4()) # generar una vez, almacenar en DB
solicitudes.Correo electrónico:(
f'{os.environ["UNIPILE_DSN"]}/api/v1/correos',
encabezados={
'X-API-KEY'os.environ['UNIPILE_API_KEY'],
'Clave de Idempotencia'llave
},
datos={'id_cuenta'os.environ['UNIPILE_CUENTA_ID'],
'a': json.vertidos([{'identificador': 'alice@acme.com'}]),
'asunto': '¡Bienvenido!', 'cuerpo': '¡Hola!'}
)Manejo de Errores y Reintentos
El código Python de producción para la API de correo electrónico necesita un manejo de excepciones adecuado, registro estructurado y reintentos automáticos con retroceso exponencial utilizando tenacidad biblioteca.
| Código HTTP | Significado | Acción |
|---|---|---|
| 202 | Aceptado - en cola para entrega | Guardar tracking_id |
| 400 | Solicitud incorrecta (campos inválidos) | Fix payload, no reintentar |
| 401 | Clave de API no válida | Comprobar UNIPILE_API_KEY |
| 403 | Cuenta no autorizada | Restablecer enlace de cuenta |
| 404 | Cuenta no encontrada | Verificar UNIPILE_ACCOUNT_ID |
| 429 | Tasa limitada | Retraso + reintentar (ver código) |
| 500 | Error del servidor | Reintentar después de 5 segundos |
import requests, os, json, logging
from tenacidad import (
reintentar, detener_después_del_intento,
espera_exponencial, reintentar_si_tipo_excepcion
)
registro.basicConfig(nivel=logging.INFORMACIÓN)
logger = logging.obtenerLogger(__nombre__)
clase ErrorDeLímiteDeTasa(Excepción):
Pasa
@reintentar(
parar=detener_después_del_intento(4),
esperaespera_exponencial(multiplicador=1, mínimo=2, máximo=30),
reintentar=reiniciar_si_es_tipo_excepcion(ErrorDeLímiteDeTasa)
)
def enviar_con_reintentoa: cadena, asunto: cadena, cuerpo: cadena) -> diccionario:
resp = requests.Correo electrónico:(
f'{os.environ["UNIPILE_DSN"]}/api/v1/correos',
encabezados={'X-API-KEY'os.environ['UNIPILE_API_KEY']},
datos={
'id_cuenta'os.environ['UNIPILE_CUENTA_ID'],
'a'json.vertidos([{'identificador': a}]),
'asunto': asunto, 'cuerpo'cuerpo
},
tiempo de espera=30
)
si resp.status_code == 429:
registrador.advertencia('Límite de tasa alcanzado, retrocediendo...')
elevar ErrorDeLímiteDeTasa()
resp.lanzar_para_estado()
return resp.json()Buenas prácticas de seguridad en Python
Para una guía completa sobre cómo proteger tu integración de API de correo electrónico, consulta la Guía de seguridad de API de correo electrónico. Aquí están los elementos esenciales específicos de Python.
os.environ o python-dotenv. Nunca pongas UNIPILE_API_KEY como un literal de cadena en tu código fuente. Si se sube accidentalmente a Git, rota la clave inmediatamente desde tu panel de control.venv o conda. Esto previene ataques de confusión de dependencias y hace tu requirements.txt Auditable. Fija versiones en producción.UNIPILE_API_KEY válido.seguimiento_id por cada correo electrónico enviado para habilitar auditorías de entrega. Utiliza el estándar de Python registro módulo - nunca imprimir() en producción. Envíe los registros a un SIEM para casos de uso en los que el cumplimiento de normativas sea importante.Errores comunes específicos de Python
Estos son los errores más comunes que cometen los desarrolladores de Python al integrar la API de correo electrónico. Si en cambio estás en Node.js, consulta nuestra Tutorial de API para enviar correos electrónicos con JavaScript.
json en vez de datos=multipart/form-data, no JSON. Utiliza siempre requests.post(..., data={...}). Utilización de json={...} devolverá un error 400. La a, ccy con copia oculta los campos son cadenas JSON dentro de los datos del formulario - usar json.dumps() para codificar la matriz de destinatarios.abrir('archivo.pdf', 'rb') - modo binario. Modo de texto'r') plantea un Error de tipo cuando se pasa a la archivos= parámetro. Para el contenido en memoria, utilice io.BytesIO.solicita library es síncrona. Llamarla dentro de un async def bloquea el bucle de eventos. Usa httpx.AsyncClient o aiohttp.ClientSession para contextos asíncronos de Python (FastAPI, vistas asíncronas de Django, scripts de asyncio).requests.post() espera para siempre. Una conexión colgada bloqueará su hilo (o Celery worker) indefinidamente. Pase siempre tiempo de espera=30 (tiempo de espera de conexión, tiempo de espera de lectura en segundos).desde datetime import datetime, timezone; datetime.now(timezone.utc). Las fechas y horas ingenuas causan errores silenciosos de horas de diferencia en despliegues multirregión.ThreadPoolExecutor concurrente.futuroso descargar a una cola de Celery.Preguntas frecuentes
Preguntas frecuentes sobre el uso de la API de correo electrónico en Python con la API unificada de correo electrónico de Unipile.
Utilice la API unificada de correo electrónico de Unipile en lugar de smtplib o una conexión SMTP directa. Instalar solicita, obtén tu clave de API y DSN del panel de control de Unipile, vincula una cuenta de Gmail u Outlook a través de OAuth, y luego haz POST a /api/v1/emails contigo account_id, a, temay cuerpo. No es necesario configurar ningún servidor SMTP, puerto 587 ni TLS en su código Python.
Django: llame a la API en una vista o comando de administración. Para Django asíncrono (3.1+), use httpx.AsyncClient en vistas asíncronas.
Frasco llama la API en un manejador de rutas del lado del servidor. Nunca la llames desde una plantilla Jinja o JavaScript del lado del cliente. Usa Flask-Celery para descargar envíos de alto volumen a trabajadores en segundo plano.
FastAPI: usar httpx.AsyncClient dentro async def endpoints. El síncrono solicita La biblioteca bloquea el bucle de eventos asincrónicos: siempre use un cliente HTTP asincrónico en FastAPI.
smtplib se conecta directamente a un servidor SMTP desde tu proceso de Python. Tú gestionas las credenciales SMTP, la configuración TLS y las peculiaridades por proveedor (contraseñas de aplicación de Gmail, autenticación moderna de Outlook). También es exclusivamente síncrono.
La API de correo electrónico Unipile es una abstracción en la nube: vincula cuentas a través de OAuth (sin credenciales SMTP en tu código para Gmail/Outlook), obtén una única API HTTP consistente para todos los proveedores, y Unipile maneja el transporte, la actualización de tokens y los reintentos. La contrapartida es que los envíos pasan por la infraestructura de Unipile en lugar de una conexión SMTP directa.
Sí, pero necesitas un cliente HTTP asíncrono, el estándar solicita La biblioteca es síncrona y bloqueará tu bucle de eventos. Usa httpx (recomendado, alternativa asíncrona de inserción) o aiohttp.
import httpx, os, json
async def enviar_correo_asincrónicamentea: cadena, asunto: cadena, cuerpo: cadena):
asincrono con httpx.AsyncClient() como cliente:
resp = await cliente.Correo electrónico:(
f'{os.environ["UNIPILE_DSN"]}/api/v1/correos',
encabezados={'X-API-KEY'os.environ['UNIPILE_API_KEY']},
datos={'id_cuenta'os.environ['UNIPILE_CUENTA_ID'],
'a': json.vertidos([{'identificador': a}]),
'asunto': asunto, 'cuerpo': cuerpo}
)
resp.lanzar_para_estado()
return resp.json()Usa una cola de tareas de Celery con un broker Redis o RabbitMQ. Cada correo electrónico se convierte en una tarea: Celery maneja la concurrencia y los reintentos automáticamente. Limita la concurrencia por trabajador para evitar límites de tasa (típicamente 5-10 envíos concurrentes por cuenta vinculada). Para envíos de marketing de muy alto volumen (millones/día), combina Unipile para envíos transaccionales basados en OAuth con un ESP dedicado para campañas masivas.
Para casos de uso más ligeros, concurrent.futures.ThreadPoolExecutor(max_workers=5) con el solicita la biblioteca es un enfoque más simple que evita la sobrecarga de Celery.
Sí. Crea una tarea de Celery que llame requests.post() al endpoint de Unipile. Los workers de Celery son procesos estándar sincronizados de Python, así que solicita Funciona perfectamente. Usa el Celery incorporado autoretry_for=(requests.exceptions.HTTPError,) con max_reintentos=3 y retraso_reintento_predeterminado=5 para reintentos automáticos ante fallos transitorios. Combinar con Clave-Idempotencia encabezados para evitar envíos duplicados en reinicios de trabajadores.
¿Aún tiene preguntas? Nuestro equipo está aquí para ayudarle.