
CI/CD Función serverless de envío de correos electrónicos con AWS SES y Lambda
por Rebeca Murillo • 20 diciembre 2023
Introducción
En este tutorial, te guiaré a través del proceso de desarrollo de un servicio simple para enviar correos electrónicos con Simple Email Service (AWS SES) y desplegarlo como una función serverless en AWS Lambda directamente desde mi repositorio de GitHub.
Estoy utilizando este servicio para mi sitio web, específicamente para el formulario de contacto. Estaba buscando una solución simple y rápida para enviar correos electrónicos de forma segura desde mi sitio web, sin exponer públicamente las credenciales de mi proveedor de correo electrónico. La función serverless es la solución más rápida y rentable para desplegar una ruta de API REST segura para un servicio tan simple como este.
Guía paso a paso
La siguiente guía se divide en varios pasos:
- Función JavaScript para enviar un correo electrónico con
nodemailer - Declaración de la función serverless con el proveedor AWS
- Autorización del repositorio de GitHub mediante un rol de AWS
- Configuración del flujo de trabajo del repositorio de GitHub
Requisitos previos para esta guía:
- Conocimientos básicos de Node.js y JavaScript
- Cuenta de AWS
- Repositorio de GitHub
1. Enviar un correo electrónico con nodemailer y aws-sdk
El siguiente código utiliza nodemailer y aws-sdk para enviar correos electrónicos a través del servicio Amazon Email Service SES.
La función handler es el punto de entrada de la función serverless, que se configurará más adelante.
La función utiliza variables de entorno para:
- la región de AWS para el servicio SES
AWS_REGION_SES - el correo electrónico predeterminado del remitente
DEFAULT_FROM_EMAIL - el correo electrónico predeterminado del destinatario
DEFAULT_TO_EMAIL
Archivo index.js
//index.js
const AWS = require("aws-sdk");
const nodemailer = require("nodemailer");
const AWS_REGION_SES = process.env.AWS_REGION_SES;
const DEFAULT_FROM_EMAIL = process.env.DEFAULT_FROM_EMAIL;
const DEFAULT_TO_EMAIL = process.env.DEFAULT_TO_EMAIL;
AWS.config.update({ region: AWS_REGION_SES });
const ses = new AWS.SES({ apiVersion: "2010-12-01" });
const transporter = nodemailer.createTransport({
SES: { ses, aws: { region: AWS_REGION_SES } },
});
exports.handler = async (event) => {
console.log("event: ", event);
const { recaptchaResponse, to, from, subject, text } = JSON.parse(event.body);
// Step 1: Validate input parameters
const inputValidationResult = validateInputParameters(
to,
from,
subject,
text
);
if (!inputValidationResult.valid) {
return response(400, { message: "Invalid input parameters" });
}
// Step 2: Send email
const emailSendingResult = await sendEmail(to, from, subject, text);
if (emailSendingResult.error) {
return response(500, {
message: "Error sending message",
error: emailSendingResult.error,
});
}
return response(200, { message: "Email sent successfully" });
};
function response(statusCode, body) {
return {
statusCode,
body: JSON.stringify(body),
};
}
function validateInputParameters(to, from, subject, text) {
if (
typeof to !== "string" ||
typeof from !== "string" ||
typeof subject !== "string" ||
typeof text !== "string"
) {
return { valid: false };
}
return { valid: true };
}
async function sendEmail(to, from, subject, text) {
const params = {
from: from || DEFAULT_FROM_EMAIL,
to: to || DEFAULT_TO_EMAIL,
subject,
text,
};
try {
let info = await transporter.sendMail(params);
console.log("Message sent: %s", info.messageId);
return { success: true };
} catch (error) {
console.error("Error sending message: ", error);
return { error };
}
}
2. Función serverless para el proveedor AWS
El siguiente código declara una función serverless que será interpretada por el paquete serverless. El archivo serverless.yml debe colocarse en la raíz del repositorio.
El despliegue de la función se realiza desde la línea de comandos:
npx serverless deploy
Consulte la documentación de AWS para Crear funciones Lambda con Node.js
En el siguiente código, podemos destacar:
- La definición del proveedor AWS
- El uso de la versión Node.js 20
- El despliegue en una región de AWS especificada como parámetro
- La definición de los permisos necesarios para la función, en este caso, son acciones de SES
- La definición de las variables de entorno a partir de los parámetros de instrucción
- La declaración de una función llamada
sendEmail, cuyo punto de entrada es la funciónhandlerdeclarada en el archivo indexindex.handler - La creación de una ruta de API REST como POST
/message
Archivo serverless.yml
# serverless.yml
service: nodemailer-ses
provider:
name: aws
runtime: nodejs20.x
region: ${param:AWS_REGION}
stage: production
iam:
role:
statements:
- Effect: "Allow"
Action:
- "ses:SendEmail"
- "ses:SendRawEmail"
Resource: "*"
environment:
AWS_REGION_SES: ${param:AWS_REGION}
DEFAULT_TO_EMAIL: ${param:DEFAULT_TO_EMAIL}
DEFAULT_FROM_EMAIL: ${param:DEFAULT_FROM_EMAIL}
functions:
sendEmail:
handler: index.handler
name: sendEmail
description: Send email using nodemailer and AWS SES
events:
- http:
path: message
method: post
Las variables de entorno se pasan con la opción param:
npx serverless deploy --param="AWS_REGION=${{ env.AWS_REGION }}" --param="DEFAULT_FROM_EMAIL=${{ env.DEFAULT_FROM_EMAIL }}" --param="DEFAULT_TO_EMAIL=${{ env.DEFAULT_TO_EMAIL }}"
Esta configuración despliega una stack de AWS CloudFormation con un modelo de Node.js, que realiza las siguientes acciones:
- Copia los archivos del proyecto a un bucket de S3
- Crea una función Lambda
- Configura API Gateway para crear una ruta REST que apunta a la función
- Crea un grupo de registros de logs en CloudWatch
- Crea un rol IAM con los permisos correspondientes para la función
Las etapas 1 y 2 se pueden ejecutar localmente en nuestra máquina. Las etapas 3 y 4 se pueden utilizar para automatizar el despliegue y actualización de nuestra función serverless desde nuestro repositorio de GitHub utilizando GitHub Actions.
3. Autorizar su repositorio de GitHub con un rol de AWS
A continuación, configuraremos nuestra cuenta de AWS para conectarla con GitHub Actions. Para ello, debemos crear un rol en AWS. Este rol se utilizará para autenticarse con AWS en el flujo de trabajo de GitHub Actions.
Consulte la documentación de AWS para obtener más detalles sobre este proceso: Usar roles de IAM para conectar GitHub Actions con acciones en AWS
3.1. Crear un proveedor OIDC para GitHub
Para empezar, debemos crear un proveedor de identidad para GitHub.
- En la consola de AWS, vaya a Identity and Access Management (IAM), bajo Access Management > Identity Providers
- Haga clic en ‘Agregar nuevo proveedor’:
- Tipo de proveedor: ‘Proveedor Open ID Connect’
- URL del proveedor:
token.actions.githubusercontent.com - Audiencia:
sts.amazonaws.com
Una vez que hayamos establecido nuestro proveedor de identidad, podemos pasar a la creación del rol.
3.2. Crear el rol de AWS
- En la consola de AWS, vaya a Identity and Access Management (IAM), bajo Access Management > ‘Roles’
- Haga clic para crear un “Nuevo rol”:
- Tipo de entidad: Identidad web.
- Seleccione el proveedor de identidad que creamos en el paso anterior. La audiencia será la predeterminada.
- La organización de GitHub es básicamente su cuenta de GitHub donde se encuentra alojado el proyecto.
- El nombre de su repositorio de GitHub
- La rama de su repositorio de GitHub que se autorizará para ejecutar el flujo de trabajo de GitHub Actions
- En la etapa de políticas de autorización, configure las reglas según las acciones que se ejecutarán en el flujo de trabajo de GitHub Actions. Para este tutorial, los permisos requeridos son:
- Acceso completo a CloudFormation: que ejecuta y orquesta el proceso de despliegue
- API Gateway: para publicar la ruta de la API REST para la función
- Acceso a S3: utilizado para copiar el contenido del repositorio
- AWS Lambda: para el despliegue de la función serverless
- Acceso a IAM: utilizado para crear un rol con los permisos específicos para la función
- Guarde el nuevo rol

Después de crear el rol, ábralo para copiar el ARN (Amazon Resource Name), que será necesario en el siguiente paso para configurar los secretos en GitHub.
4. Flujo de trabajo de GitHub Actions para el despliegue serverless en AWS
Por último, el repositorio está configurado para ejecutar el despliegue con un flujo de trabajo de GitHub Actions.
El siguiente archivo declara un flujo de trabajo de GitHub Actions que realiza las siguientes acciones:
- Se ejecuta manualmente, para personalizar el desencadenador del flujo de trabajo, consulte la documentación de GitHub sobre los eventos que desencadenan los flujos de trabajo
- Define los permisos para la conexión con AWS
- Define las variables de entorno a partir de los secretos de GitHub
- Se conecta a AWS a través de un rol de AWS con la acción
aws-actions/configure-aws-credentials - Despliega la función con el paquete de Node.js serverless y pasa los parámetros
npx serverless deploy --param="AWS_REGION=${{ env.AWS_REGION }}"…
name: Deploy to AWS Lambda
on:
workflow_dispatch:
permissions:
id-token: write
contents: read
env:
AWS_REGION: ${{ secrets.AWS_REGION }}
AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN }}
DEFAULT_FROM_EMAIL: ${{ secrets.DEFAULT_FROM_EMAIL }}
DEFAULT_TO_EMAIL: ${{ secrets.DEFAULT_TO_EMAIL }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Deploy Lambda function
run: |
run: |
npx serverless deploy --param="AWS_REGION=${{ env.AWS_REGION }}" \
--param="DEFAULT_FROM_EMAIL=${{ env.DEFAULT_FROM_EMAIL }}" \
--param="DEFAULT_TO_EMAIL=${{ env.DEFAULT_TO_EMAIL }}"
Configure los secretos en su repositorio de GitHub, en Configuración > Secretos y variables > Acciones
- AWS_ROLE_ARN: el ARN del rol creado en el paso 3.2
- AWS_REGION: la región de AWS donde se desplegarán los recursos
- DEFAULT_FROM_EMAIL: la dirección de correo electrónico predeterminada para la función
- DEFAULT_TO_EMAIL: la dirección de correo electrónico predeterminada para la función
Para ejecutar el flujo de trabajo en su repositorio de GitHub, vaya a Acciones > seleccione el flujo de trabajo > ejecutar flujo de trabajo

Si la configuración es correcta, verá el progreso de su despliegue en su cuenta de AWS, en CloudFormation.

Identifique el endpoint de su servicio en la pestaña “Outputs” de su stack de CloudFormation. Ahora puede llamar a su función utilizando la ruta completa.
En nuestro ejemplo, la función está disponible llamando a la siguiente ruta:
curl -X POST https://<unique id for resource>.execute-api.eu-west-3.amazonaws.com/production/message \
-H "Content-Type: application/json" \
-d '{
"to": "myemail@gmail.com",
"from": "myemail@gmail.com",
"subject": "my new email",
"text": "my email content text"
}'
Ahora puedes comenzar a llamar a este endpoint para enviar correos electrónicos con el servicio AWS SES.
Para ir más allá, podemos pensar en cómo asegurar nuestra función serverless. Esto será tema de un nuevo tutorial que vendrá pronto…
