Dev Life
Creación de flujos de trabajo de emails transaccionales para restablecer contraseñas con la API de Mailgun
Emails transaccionales son emails automatizados activados por acciones de los usuarios en un sitio web, una aplicación o un servicio. A diferencia de emails de marketing, que promocionan productos o servicios, los emails transaccionales ofrecen información fundamental, a menudo en tiempo real.
Los flujos de trabajo de emails transaccionales van un paso más allá y automatizan una secuencia de emails a partir de las acciones de los usuarios para garantizar una comunicación oportuna y una buena experiencia de usuario. Las empresas confían en servicios como Mailgun para sus emails transaccionales porque son rápidos, relevantes y se activan mediante acciones inmediatas de los usuarios. Por ejemplo, el flujo de trabajo permite a los usuarios restablecer rápidamente su contraseña y recuperar el acceso a su cuenta.
En este tutorial, descubrirás cómo crear un flujo de trabajo de emails transaccionales para restablecer contraseñas con la API de Mailgun.
Configurar una cuenta de Mailgun
Si aún no tienes una cuenta de Mailgun, lo primero que debes hacer es crear una. Asegúrate de revisar el email asociado a tu cuenta de Mailgun y de verificar tu cuenta:

A continuación, introduce tu número de teléfono y el código de autorización proporcionado para activar tu cuenta:

Una vez configurada tu cuenta de Mailgun, llega el momento de obtener una clave de API para empezar a utilizar el servicio.
Obtención de una clave de API
Para que tu aplicación interactúe con los servicios de Mailgun, es necesario autenticarla a través de una clave de API. Para conseguir tu clave de API, ve al panel de control de Mailgun y selecciona Get started a la izquierda para consultar la guía Get started guide:

Haz clic en el botón Get started que aparece bajo Create an API Key. A continuación, incluye una descripción que refleje bien el propósito de la clave de la API. Por ejemplo, si la clave de API es para procesos de autenticación de usuarios, puedes llamarla algo como user_authentication y hacer clic en Create Key:

Asegúrate de copiar y guardar esta clave en un lugar seguro, ya que esta es la única vez que se mostrará:

Creación de la interfaz de la aplicación con FastAPI
Para crear la interfaz de la aplicación que procesa el flujo de trabajo de los emails, lo primero que debes hacer es instalar las bibliotecas necesarias. Antes de hacerlo, crea un directorio de proyecto ejecutando mkdir my_fastapi_app y pon en marcha un entorno virtual en el directorio.
Después, instala las bibliotecas necesarias con el comando siguiente:
pip install fastapi jinja2 requests uvicorn python-multipart
Aquí debes instalar lo siguiente:
fastapi, que sirve como el principal framework de backendjinja2, que proporciona las plantillas de frontendrequests, que envía peticiones HTTP a la API de Mailgun
Aunque FastAPI sirve sobre todo para crear API, la incorporación de la biblioteca jinja2 permite el desarrollo full-stack, pero requiere una configuración adecuada de los directorios. La estructura de tu proyecto debe seguir este formato:
my_fastapi_app/
│── main.py # Archivo principal de la aplicación FastAPI
│── users.json # Archivo JSON donde se guardan los datos de los usuarios
│── templates/ # Carpeta de plantillas HTML
│ ├── login.html # Página de inicio de sesión
│ ├── home.html # Página de inicio
│ ├── passwordReset.html # Página para restablecer la contraseña
│── static/ # Carpeta de archivos estáticos
│ ├── styles.css # Estilos para los archivos login.html y passwordReset.html
Puedes encontrar todo el código de este tutorial en este repositorio de GitHub.
Una vez definida la estructura del proyecto, puedes copiar y pegar el código en los archivos correspondientes o bien clonar el anterior repositorio de GitHub con el siguiente comando:
git clone https://github.com/EphraimX/building-transactional-email-workflows-for-password-resets-with-mailgun-api.git
Veamos la primera sección del archivo main.py. En este archivo es donde sucede toda la magia, ya que conecta las operaciones del frontend y del backend:
import osrnimport jsonrnimport uuidrnimport requestsrnimport urllib.parsernfrom fastapi import FastAPI, Request, Formrnfrom pydantic import BaseModelrnfrom fastapi.responses import HTMLResponsernfrom fastapi.staticfiles import StaticFilesrnfrom fastapi.templating import Jinja2Templatesrnrnapp = FastAPI()rnapp.mount("/static", StaticFiles(directory="static"), name="static")rntemplates = Jinja2Templates(directory="templates")rnrnclass loginData(BaseModel):rn username: strrn password: strrnrn@app.get("/login", response_class=HTMLResponse)rnasync def login(request: Request):rn return templates.TemplateResponse("login.html", {"request": request, "message": ""})rnrn@app.get("/passwordResetView/", response_class=HTMLResponse)rnasync def password_reset_view(request: Request):rn return templates.TemplateResponse(rn "passwordReset.html", {"request": request, "message": ""}rn )rnrn@app.post("/home", response_class=HTMLResponse)rnasync def password_reset(rn request: Request, email_address: str = Form(...), password: str = Form(...)rn):rnrn # Open and read the JSON DB filern try:rn with open("users.json", "r") as file:rn users = json.load(file)rn except (FileNotFoundError, json.JSONDecodeError):rn users = []rnrn for user in users:rnrn if user["email_address"] == email_address and user["password"] == password:rn return templates.TemplateResponse("home.html", {"request": request})rnrn # If no match was found, return an error messagern return templates.TemplateResponse(rn "login.html",rn {rn "request": request,rn "message": "Email or Password Incorrect. If you cannot remember your password, kindly reset it.",rn },rn )rn
En este código, la aplicación gestiona la autenticación mostrando las páginas de inicio de sesión y de restablecer contraseñas, al tiempo que comprueba las credenciales en users.json. La aplicación inicializa FastAPI, configura las plantillas y define un modelo loginData para la validación. Las rutas /login y /passwordResetView/ ofrecen sus respectivas páginas, mientras que /home comprueba las credenciales, redirige a los usuarios válidos o muestra los errores correspondientes. La función de restablecer contraseñas genera un enlace único mediante uuid y lo envía a través de la API de Mailgun.
Para probarlo, abre tu base de datos provisional (users.json) y pega lo siguiente:
[rn {rn "username" : "sinchy",rn "email_address" : "sinch@gmail.com",rn "password" : "sinch12345"rn },rn {rn "username" : "grinchy",rn "email_address" : "rinch@gmail.com",rn "password" : "grinch67890"rn }rn]
También debes autorizar estas direcciones de email a través del panel de control de Mailgun si vas a usar un dominio sandbox.
Inicia la aplicación ejecutando el comando uvicorn main:app –reload. A continuación, ve a http://localhost:8000/login, donde deberías ver esto:

Si intentas iniciar sesión con alguna de las credenciales del archivo users.json, accederás a la página de inicio:

Si introduces una información de inicio de sesión incorrecta, verás lo siguiente:

Configuración de la conexión a Mailgun
Para enviar emails con Mailgun, es preciso establecer una conexión entre la aplicación y el servicio de envío de emails de Mailgun.
Para ello, abre main.py y pega el código siguiente, pero cambia , y por valores que sean válidos:
def send_simple_message(API_KEY):rn response = requests.post(rn "https://api.mailgun.net/v3/<SANDBOX_URL>/messages",rn auth=("api", f"{API_KEY}"),rn data={"from": "Mailgun Sandbox <postmaster@<SANDBOX_URL>>",rn "to": "<RECIPIENT_NAME> <RECIPIENT_EMAIL>",rn "subject": "Hello <RECIPIENT_NAME>",rn "text": "Congratulations <RECIPIENT_NAME>, you just sent an email with Mailgun! You are truly awesome!"rn }rn )rnrn print(response.json())rn return responsern
Para realizar pruebas, pasa tu clave de API directamente al llamar a la función:
send_simple_message("your-api-key-here")
Pon en marcha el script ejecutando lo siguiente:
python main.py
Si todo ha ido bien, la función imprimirá una respuesta similar a esta:
{
"id": "",
"message": "Queued. Thank you."
}
Esto confirma que la solicitud de email se ha puesto en la cola correctamente mediante Mailgun.
Diseño y configuración de la plantilla de email
Para trabajar en la lógica para restablecer contraseñas, tienes que diseñar la plantilla del email que se enviará al usuario. Mailgun ofrece dos formas de diseñar un email: en HTML o con el creador de plantillas de arrastrar y soltar de Mailgun. Aquí usarás el enfoque de HTML.
Para empezar con el diseño, entra en el panel de control de Mailgun y ve a Send > Sending > Templates:

Después, haz clic en Create message template, selecciona la opción para programarlo en HTML y haz clic en la plantilla en blanco (Blank):

Rellena el formulario de la plantilla con el nombre, la descripción y los detalles del email:

A continuación, desplázate hasta la pestaña Editor y copia y pega el código del archivo email_template.html :

Al seleccionar la pestaña Test Data, verás dos variables dinámicas: username y reset_link.
Selecciona la pestaña Preview para ver la plantilla:

Por último, haz clic en el botón Create para crearla.
Implementación de la funcionalidad de envío de emails
Cuando la plantilla de email esté lista, llegará el momento de crear la función de envío de emails. Abre el archivo main.py y pega el siguiente código:
@app.post("/resetPassword/")rnasync def reset_password(request: Request, email_address: str = Form(...)):rnrn # Open and read the JSON DB filern try:rn with open("users.json", "r") as file:rn users = json.load(file)rn except (FileNotFoundError, json.JSONDecodeError):rn users = []rnrn for user in users:rn if user["email_address"] == email_address:rnrn email_address = user["email_address"]rn username = user["username"]rn reset_link = await generate_password_reset_link(email_address)rnrn response = await send_password_reset_email(rn username, email_address, reset_linkrn )rnrn if response["success"]:rnrn return templates.TemplateResponse(rn "login.html",rn {rn "request": request,rn "message": "Kindly check your email to reset your password.",rn },rn )rnrn else:rnrn return templates.TemplateResponse(rn "passwordReset.html",rn {rn "request": request,rn "message": "Unfortunately, we ran into an error while trying to reset your password. Kindly try again, and if the issue persists, please contact support.",rn },rn )rnrn # If no match was found, return an error messagern return templates.TemplateResponse(rn "passwordReset.html",rn {rn "request": request,rn "message": "Unfortunately, we do not have a record of your email. Kindly reach out to the site administrator.",rn },rn )rnrnasync def send_password_reset_email(username, email_address, reset_link):rnrn API_KEY = os.getenv("API_KEY") # Fetch API key from environmentrnrn if not API_KEY:rn print("Error: API_KEY is not set!")rn return {"success": False, "error": "API key is missing"}rnrn try:rn response = requests.post(rn "https://api.mailgun.net/v3/<SANDBOX_URL>/messages",rn auth=("api", API_KEY),rn data={rn "from": "Mailgun Sandbox <postmaster@<SANDBOX_URL>>",rn "to": f"{username} <{email_address}>",rn "subject": f"Hello {username}",rn "template": "<EMAIL_TEMPLATE_NAME>",rn "h:X-Mailgun-Variables": json.dumps(rn {"reset_link": reset_link, "username": username}rn ),rn },rn )rnrn if response.status_code == 200:rn print(f"Password reset email sent successfully to {email_address}")rn return {"success": True, "message": "Email sent successfully"}rn else:rn print(rn f"Failed to send email. Status Code: {response.status_code}, Response: {response.text}"rn )rn return {"success": False, "error": response.text}rnrn except requests.exceptions.RequestException as e:rn print(f"Request failed: {str(e)}")rn return {"success": False, "error": str(e)}rnrnasync def generate_password_reset_link(rn email_address, base_url="https://mailgunny.com/reset-password"rn):rn token = uuid.uuid4()rn encoded_email = urllib.parse.quote(email_address)rn return f"{base_url}?email={encoded_email}&token={token}"rn
En este código, la función reset_password (@app.post("/resetPassword/")) procesa las solicitudes para restablecer contraseñas comprobando si el email enviado figura en la base de datos de users.json. Si es así, genera un enlace de restablecimiento único mediante generate_password_reset_link(email_address) y envía el email para restablecer la contraseña a través de send_password_reset_email(username, email_address, reset_link). A continuación, la función devuelve un mensaje de éxito si el email se ha enviado o bien muestra un mensaje de error si no es así. Si no se encuentra ningún email coincidente, se devuelve la respuesta de error correspondiente.
Nota: Sustituye la variable API_KEY por la clave de tu cuenta de Mailgun.
Para probar la implementación, ejecuta uvicorn main:app –reload y abre http://localhost:8000/login. Haz clic en el enlace Forgot Password? :

Introduce un email que se encuentre en tu archivo users.json y haz clic en Reset Password:

Si va bien, volverás a la página de inicio de sesión, donde verás las instrucciones para revisar tu email. Deberías ver el siguiente email en la bandeja de entrada del destinatario:

Integración con eventos de aplicaciones
La integración con los eventos de la aplicación mediante webhooks permite que tu sistema responda de forma automática a las actualizaciones en tiempo real de servicios externos.
En esencia, un webhook es una petición HTTP de devolución de llamada. Siempre que se produce un evento específico en una aplicación externa (como la confirmación de un pago, el registro de un usuario o una solicitud para restablecer contraseñas), el servicio envía una petición a tu URL de webhook predefinida. De este modo se logra una automatización sin fisuras que reduce la necesidad de intervención manual o de sondeos periódicos. Al configurar tu aplicación para que intercepte estos eventos de webhook, puedes activar acciones pertinentes como actualizar una base de datos, enviar notificaciones o procesar transacciones en el instante en que se produzca el evento.
Para integrar los webhooks con Mailgun, tienes que crear un punto de conexión. Para ello, abre el archivo main.py y pega el código siguiente al final:
@app.post("/webhooks/password-reset")rnasync def handle_webhook(request: Request):rn data = await request.json()rn print(f"Password reset email clicked: {data}")rn return {"status": "received", "status_code": 200}rn
Vuelve al panel de control y dirígete a Send > Sending > Webhooks:

Haz clic en Add webhook en la esquina superior derecha. En la lista desplegable de tipos de eventos, selecciona Delivered Messages, y en el campo URL, introduce la ruta a tu URL del webhook. En este caso, se trata de https:///webhooks/password-reset. Al terminar, haz clic en Create webhook:

Prueba el webhook. El resultado debería tener este aspecto:

En el lado del servidor, tendrías que recibir el siguiente mensaje:
Password reset email clicked: {'signature': {'token': '41929b08bb287b8a0b157f7834844b6bc483b7b090692a2b15', 'timestamp': '1739785355', 'signature': '117f70dcf985c72df30c07b02898dd458d1cbbad29d0457ccee8c4497f89c5ac'}, 'event-data': {'id': 'CPgfbmQMTCKtHW6uIWtuVe', 'timestamp': 1521472262.908181, 'log-level': 'info', 'event': 'delivered', 'delivery-status': {'tls': True, 'mx-host': 'smtp-in.example.com', 'code': 250, 'description': '', 'session-seconds': 0.4331989288330078, 'utf8': True, 'attempt-no': 1, 'message': 'OK', 'certificate-verified': True}, 'flags': {'is-routed': False, 'is-authenticated': True, 'is-system-test': False, 'is-test-mode': False}, 'envelope': {'transport': 'smtp', 'sender': 'bob@sandbox9199067bcb654265aae9ce9e308b6150.mailgun.org', 'sending-ip': '209.61.154.250', 'targets': 'alice@example.com'}, 'message': {'headers': {'to': 'Alice ', 'message-id': '20130503182626.18666.16540@sandbox9199067bcb654265aae9ce9e308b6150.mailgun.org', 'from': 'Bob ', 'subject': 'Test delivered webhook'}, 'attachments': [], 'size': 111}, 'recipient': 'alice@example.com', 'recipient-domain': 'example.com', 'storage': {'url': 'https://se.api.mailgun.net/v3/domains/sandbox9199067bcb654265aae9ce9e308b6150.mailgun.org/messages/message_key', 'key': 'message_key'}, 'campaigns': [], 'tags': ['my_tag_1', 'my_tag_2'], 'user-variables': {'my_var_1': 'Mailgun Variable #1', 'my-var-2': 'awesome'}}}
Esto puede variar en función del email que se envíe.
Seguimiento y gestión de emails
Peter Drucker, un famoso consultor de gestión, dijo una vez:
Lo que no se puede medir, no se puede mejorar.
En este sentido, Mailgun ofrece herramientas para comprobar el rendimiento de los emails enviados. Por ejemplo, la página Send > Sending > Analytics ayuda a medir qué porcentaje de emails se están entregando, abriendo y recibiendo clics:

En la página Send > Reporting > Metrics, puedes consultar información detallada sobre el rendimiento de tus emails:

También puedes elegir las métricas que te interesa ver a lo largo de un período determinado:

Mailgun también te proporciona registros por si quieres investigar a fondo los eventos entregados y analizar los errores cuando se produzcan:

Por último, Mailgun ofrece una clasificación de los rebotes para que puedas comprobar cuántos de tus emails no llegan a su destino. También te permite saber cuántos de esos rebotes son críticos:

En resumen
En este tutorial, has aprendido a crear un flujo de trabajo completo de emails transaccionales para restablecer contraseñas con la API de Mailgun. Tras configurar una cuenta de Mailgun y obtener una clave de API, has creado la interfaz de una aplicación mediante FastAPI. Después, has diseñado y configurado una plantilla de email, has implementado la función de enviar emails y has integrado webhooks para hacer un seguimiento de los eventos en tiempo real.
Las operaciones de los emails transaccionales son de suma importancia a la hora de proporcionar una experiencia de usuario fluida y segura, sobre todo para restablecer contraseñas. Mailgun puede ayudarte a automatizar este proceso, mejorando la seguridad y la productividad, y acabando con la intervención manual. La adopción de este enfoque en tus proyectos no solo incrementará la satisfacción de la clientela, sino que también contribuirá a conservar un sistema de autenticación fiable.