Enviando correos electronicos con AWS Lambda y SES desde un formulario HTML
- 14 golang
- 8 serverless
Serie sobre Serverless
Parte I: Sirviendo sitios web estáticos con S3 y CloudFront, es recomendable revisar esa parte antes de empezar con esta para entender cómo llegamos hasta aquí.
Parte II: Enviando emails con AWS Lambda y SES desde un formulario HTML, Estás aquí.
Introducción
Este artículo forma parte de la serie sobre tecnologías serverless. Aquí veremos cómo crear una función serverless en AWS Lambda para enviar un email desde un formulario HTML en el sitio. El código fuente lo podés encontrar acá para la versión en Go, pero si preferís Node.js, podés usar esta versión.
Framework Serverless
Como de costumbre, voy a usar el framework Serverless para gestionar nuestras funciones. Creamos el proyecto:
mkdir techsquad-functions && cd techsquad-functions && serverless create -t aws-go
# OUTPUT:
# Serverless: Generating boilerplate...
# _______ __
# | _ .-----.----.--.--.-----.----| .-----.-----.-----.
# | |___| -__| _| | | -__| _| | -__|__ --|__ --|
# |____ |_____|__| \___/|_____|__| |__|_____|_____|_____|
# | | | The Serverless Application Framework
# | | serverless.com, v1.36.1
# -------'
#
# Serverless: Successfully generated boilerplate for template: "aws-go"
# Serverless: NOTE: Please update the "service" property in serverless.yml with your service name
Después de crear el proyecto, actualizamos el manifiesto de serverless de la siguiente manera:
service: sendMail
frameworkVersion: ">=1.28.0 <2.0.0"
provider:
name: aws
runtime: go1.x
region: us-east-1
memorySize: 128
versionFunctions: false
stage: 'prod'
iamRoleStatements:
- Effect: "Allow"
Action:
- "ses:*"
- "lambda:*"
Resource:
- "*"
package:
exclude:
- ./**
include:
- ./send_mail/send_mail
functions:
send_mail:
handler: send_mail/send_mail
events:
- http:
path: sendMail
method: post
Las partes interesantes aquí son los permisos de IAM y la función send_mail
. El resto es bastante estándar: definimos una función y el evento HTTP POST para el API Gateway, donde se encuentra nuestro ejecutable, y también solicitamos permisos para enviar emails a través de SES.
Desplegamos la función
make deploy
# OUTPUT:
# rm -rf ./send_mail/send_mail
# env GOOS=linux go build -ldflags="-s -w" -o send_mail/send_mail send_mail/main.go
# sls deploy --verbose
# ...
# Serverless: Stack update finished...
# Service Information
# service: sendMail
# stage: prod
# region: us-east-1
# stack: sendMail-prod
# api keys:
# None
# endpoints:
# POST - https://m8ebtlirjg.execute-api.us-east-1.amazonaws.com/prod/sendMail
# functions:
# send_mail: sendMail-prod-send_mail
# layers:
# None
#
# Stack Outputs
# ServiceEndpoint: https://m8ebtlirjg.execute-api.us-east-1.amazonaws.com/prod
# ServerlessDeploymentBucketName: sendmail-prod-serverlessdeploymentbucket-1vbmb6gwt8559
Todo se ve bien, ¿qué sigue? El código fuente.
Lambda
Este es básicamente el código fuente completo de esta función. Como verás, es bastante simple:
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ses"
)
type Response events.APIGatewayProxyResponse
type RequestData struct {
Email string
Message string
}
// Este valor podría ser una variable de entorno
const (
Sender = "[email protected]"
Recipient = "[email protected]"
CharSet = "UTF-8"
)
func Handler(ctx context.Context, request events.APIGatewayProxyRequest) (Response, error) {
fmt.Printf("Request: %+v\n", request)
fmt.Printf("Processing request data for request %s.\n", request.RequestContext.RequestID)
fmt.Printf("Body size = %d.\n", len(request.Body))
var requestData RequestData
json.Unmarshal([]byte(request.Body), &requestData)
fmt.Printf("RequestData: %+v", requestData)
var result string
if len(requestData.Email) > 0 && len(requestData.Message) > 0 {
result, _ = send(requestData.Email, requestData.Message)
}
resp := Response{
StatusCode: 200,
IsBase64Encoded: false,
Body: result,
Headers: map[string]string{
"Content-Type": "application/json",
"X-MyCompany-Func-Reply": "send-mail-handler",
},
}
return resp, nil
}
func send(Email string, Message string) (string, error) {
// Esto podría ser una variable de entorno
sess, err := session.NewSession(&aws.Config{
Region: aws.String("us-east-1")},
)
// Creamos una sesión en SES.
svc := ses.New(sess)
// Armamos el correo electrónico.
input := &ses.SendEmailInput{
Destination: &ses.Destination{
CcAddresses: []*string{},
ToAddresses: []*string{
aws.String(Recipient),
},
},
Message: &ses.Message{
Body: &ses.Body{
Html: &ses.Content{
Charset: aws.String(CharSet),
Data: aws.String(Message),
},
Text: &ses.Content{
Charset: aws.String(CharSet),
Data: aws.String(Message),
},
},
Subject: &ses.Content{
Charset: aws.String(CharSet),
Data: aws.String(Email),
},
},
// Usamos el mismo remitente porque necesita estar validado en SES.
Source: aws.String(Sender),
// Descomentar para usar un conjunto de configuración
//ConfigurationSetName: aws.String(ConfigurationSet),
}
// Intentamos enviar el correo electrónico.
result, err := svc.SendEmail(input)
// Mostramos los mensajes de error si los hay.
if err != nil {
if aerr, ok := err.(awserr.Error); ok {
switch aerr.Code() {
case ses.ErrCodeMessageRejected:
fmt.Println(ses.ErrCodeMessageRejected, aerr.Error())
case ses.ErrCodeMailFromDomainNotVerifiedException:
fmt.Println(ses.ErrCodeMailFromDomainNotVerifiedException, aerr.Error())
case ses.ErrCodeConfigurationSetDoesNotExistException:
fmt.Println(ses.ErrCodeConfigurationSetDoesNotExistException, aerr.Error())
default:
fmt.Println(aerr.Error())
}
} else {
fmt.Println(err.Error())
}
return "hubo un error inesperado", err
}
fmt.Println("Email enviado a la dirección: " + Recipient)
fmt.Println(result)
return "¡Enviado!", err
}
func main() {
lambda.Start(Handler)
}
El código es bastante directo: solo espera 2 parámetros, enviará un email y devolverá “sent!” si todo salió bien. Podés depurar y compilar tu función antes de subirla ejecutando el comando make
(muy útil), y si usás make deploy
, ahorrarás tiempo al desplegar solo los archivos que funcionan.
SES
Para que esto funcione, vas a necesitar verificar/validar tu dominio en SES.
Andá a SES->Domains->Verify a New Domain
.
Después de ingresar tu dominio, verás algo como esto:
Como no tengo este dominio en Route53, no tengo el botón para agregar los registros (lo que lo haría más simple y rápido), pero es fácil, solo tenés que crear unos registros DNS y esperar unos minutos hasta que obtengas algo como esto:
Después de eso, probalo
serverless invoke -f send_mail -d '{ "Email": "[email protected]", "Message
": "test" }'
# OUTPUT:
{
"statusCode": 200,
"headers": {
"Content-Type": "application/json",
"X-MyCompany-Func-Reply": "send-mail-handler"
},
"body": ""
}
Después de presionar Enter, el mensaje apareció de inmediato en mi bandeja de entrada :).
Otra opción es usar httpie
echo '{ "email": "[email protected]", "message": "test2" }' | http https://m8ebtlirjg.execute-api.us-east-1.amazonaws.com/prod/sendMail
# OUTPUT:
# HTTP/1.1 200 OK
# ...
# sent!
O curl
curl -i -X POST https://m8ebtlirjg.execute-api.us-east-1.amazonaws.com/prod/sendMail -d '{ "email": "[email protected]", "message": "test3" }'
# OUTPUT:
# HTTP/2 200
# ...
# sent!
Y eso es todo por ahora, nos vemos en el próximo artículo.
Errata
Si encontrás algún error o tenés alguna sugerencia, por favor enviame un mensaje para que lo corrija.
No tienes cuenta? Regístrate aqui
Ya registrado? Iniciar sesión a tu cuenta ahora.
-
Comentarios
Online: 0
Por favor inicie sesión para poder escribir comentarios.