Pre-requisites
- Require to setup Serverless on workstation and get it configured with AWS as cloud provider and AWS access key - programmatic user
Simple Email Service (SES) setup on AWS
Go to SES → Create identity sender email : blah.aws@gmail.com and receiver email : blah-blah@gmail.com
Verify the email
Simple Email Service (SES) and Serverless
- Create a Node.js starter project on serverless workstation
- Update the handler.js file which has a createContact lambda function
"use strict";
const AWS = require("aws-sdk");
const ses = new AWS.SES();
module.exports.createContact = async (event, context) => {
console.log("Received:::", event);
const { to, from, subject, message } = JSON.parse(event.body);
if (!to || !from || !subject || !message) {
return {
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Origin": "*",
},
statusCode: 400,
body: JSON.stringify({ message: " to or from... are not set properly!" }),
};
}
const params = {
Destination: {
ToAddresses: [to],
},
Message: {
Body: {
Text: { Data: message },
},
Subject: { Data: subject },
},
Source: from,
};
try {
await ses.sendEmail(params).promise();
return {
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Origin": "*",
},
statusCode: 200,
body: JSON.stringify({
message: "email sent successfully!",
success: true,
}),
};
} catch (error) {
console.error(error);
return {
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Origin": "*",
},
statusCode: 400,
body: JSON.stringify({
message: "The email failed to send",
success: true,
}),
};
}
};
- Update the serverless.yml file.
- region: ap-south-1 Mumbai
- functions: createContact
- events: -http
- path: /mail
- method: POST
service: aws-ses-project
frameworkVersion: '3'
provider:
name: aws
runtime: nodejs14.x
region : ap-south-1
functions:
createContact:
handler: handler.createContact
events:
- http:
path: /mail
method: POST
- Deploy the changes using
serverless deploy
which will create- Lambda function - createContact
createContact: aws-ses-project-dev-createContact (1.7 kB)
Improve API performance – monitor it with the Serverless Dashboard: run "serverless"
-
API Gateway - /mail POST method - Trigger to invoke createContact - API Gateway POST endpoint: *https://oadnufagsd.execute-api.ap-south-1.amazonaws.com/dev/mail
*
- Testing the API and Lambda function using POSTMAN - POST method
- Checking the POST method url: https://oadnufagsd.execute-api.ap-south-1.amazonaws.com/dev/mail
- body :
{ "to": "<-sender-email->@gmail.com",
"from": "<-receiver-email->@gmail.com",
"subject": "test mail",
"message": "send successfully" }
NOTE The response of the POST method says that API is successfully set and triggered the event but "the email is failed to send"
To diagnose and debug the error go to API POST -> Logs method(Observability Tool - Metrics/Logs/Traces) and choose *Open in CloudWatch * in Logs.
The error message is as follows :
2022-04-25T18:54:54.759Z 1623581b-c472-4002-bc06-519766d1f0be ERROR AccessDenied: User `arn:aws:sts::673040272970:assumed-role/aws-ses-project-dev-ap-south-1-lambdaRole/aws-ses-project-dev-createContact' is not authorized to perform `ses:SendEmail' on resource `arn:aws:ses:ap-south-1:673040272970:identity/<-sender-email->@gmail.com'
at Request.extractError (/var/runtime/node_modules/aws-sdk/lib/protocol/query.js:50:29)
at Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:106:20)
at Request.emit (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:78:10)
at Request.emit (/var/runtime/node_modules/aws-sdk/lib/request.js:686:14)
at Request.transition (/var/runtime/node_modules/aws-sdk/lib/request.js:22:10)
at AcceptorStateMachine.runTo (/var/runtime/node_modules/aws-sdk/lib/state_machine.js:14:12)
at /var/runtime/node_modules/aws-sdk/lib/state_machine.js:26:10
at Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:38:9)
at Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:688:12)
at Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:116:18) {
code: 'AccessDenied',
time: 2022-04-25T18:54:54.756Z,
requestId: 'd2111db9-a75d-4c37-bbee-e4d4738268ea',
statusCode: 403,
retryable: false,
retryDelay: 22.98167156490245
}
What caused the error ? : Simple Email Service (SES) and Lambda are two different AWS managed services which does not have access to each other by default.
Solution: Setup an iam role with policy in a way that lambda can access ses functionalities
Create an IAM Role for Lambda to access SES
- Go to IAM → Role → AWS Service
Use case : enable Lambda (radio button or select from drop down list).
Set the name of the role :
Lambda-SES-Full-Access
- NOTE: Message field says : email sent successfully
- Confirming on gmail
Creating a frontend to send email using Next.js
- Go to Setup Next.js app in workstation. (To know more Next.js)
- Create a Next.js application in aws-ses-project folder (same as above)
npx create-next-app nextjs-blog --use-npm --example "https://github.com/vercel/next-learn/tree/master/basics/learn-starter
-
cd nextjs-blog
andrun build npm run dev
which will deploy a Welcome Next.js page accessible onhttp://localhost:3000
. Modifying the index.js by creating mail form replacing the welcome index page
import Head from "next/head";
export default function Home() {
const sendMessage = async (event) => {
event.preventDefault();
/*
"to": "youremail@gmail.com",
"from": "email@gmail.com",
"subject": "this needs to work!",
"message": "Hello there,!@"
*/
const res = await fetch(
# Replace this by your api method gateway
"https://3t66ov5uxg.execute-api.us-east-1.amazonaws.com/dev/contact-us",
{
body: JSON.stringify({
to: event.target.to.value,
from: event.target.from.value,
subject: event.target.subject.value,
message: event.target.message.value,
}),
headers: {
"Content-Type": "application/json",
},
method: "POST",
}
);
const result = await res.json();
console.log(result);
};
return (
<div className='container'>
<Head>
<title>Contact us</title>
<link rel='icon' href='/favicon.ico' />
</Head>
<main>
<h1 className='title'>Contact us</h1>
<div className='grid'>
<form onSubmit={sendMessage}>
<label htmlFor='to'>To: </label>
<input id='to' name='to' type='text' required />
<label htmlFor='from'>From: </label>
<input
id='from'
name='from'
type='text'
autoComplete='from'
required
/>
<label htmlFor='subject'>Subject: </label>
<input id='subject' name='subject' type='text' required />
<label htmlFor='message'>Message: </label>
<input id='message' name='message' type='text' required />
<button type='submit'>Send</button>
</form>
</div>
</main>
<footer>Powered by Awesomness</footer>
<style jsx>{`
/* Style inputs */
input[type="text"],
select {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
/* Style the submit button */
input[type="submit"] {
width: 100%;
background-color: #04aa6d;
color: white;
padding: 14px 20px;
margin: 8px 0;
border: none;
border-radius: 4px;
cursor: pointer;
}
/* Add a background color to the submit button on mouse-over */
input[type="submit"]:hover {
background-color: #45a049;
}
.container {
min-height: 100vh;
padding: 0 0.5rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
main {
padding: 5rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
footer {
width: 100%;
height: 100px;
border-top: 1px solid #eaeaea;
display: flex;
justify-content: center;
align-items: center;
}
footer img {
margin-left: 0.5rem;
}
footer a {
display: flex;
justify-content: center;
align-items: center;
}
a {
color: inherit;
text-decoration: none;
}
.title a {
color: #0070f3;
text-decoration: none;
}
.title a:hover,
.title a:focus,
.title a:active {
text-decoration: underline;
}
.title {
margin: 0;
line-height: 1.15;
font-size: 4rem;
}
.title,
.description {
text-align: center;
}
.description {
line-height: 1.5;
font-size: 1.5rem;
}
code {
background: #fafafa;
border-radius: 5px;
padding: 0.75rem;
font-size: 1.1rem;
font-family: Menlo, Monaco, Lucida Console, Liberation Mono,
DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;
}
.grid {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
max-width: 800px;
margin-top: 3rem;
}
.card {
margin: 1rem;
flex-basis: 45%;
padding: 1.5rem;
text-align: left;
color: inherit;
text-decoration: none;
border: 1px solid #eaeaea;
border-radius: 10px;
transition: color 0.15s ease, border-color 0.15s ease;
}
.card:hover,
.card:focus,
.card:active {
color: #0070f3;
border-color: #0070f3;
}
.card h3 {
margin: 0 0 1rem 0;
font-size: 1.5rem;
}
.card p {
margin: 0;
font-size: 1.25rem;
line-height: 1.5;
}
.logo {
height: 1em;
}
@media (max-width: 600px) {
.grid {
width: 100%;
flex-direction: column;
}
}
`}</style>
<style jsx global>{`
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue,
sans-serif;
}
* {
box-sizing: border-box;
}
`}</style>
</div>
);
}
Cross-Origin Resource Sharing (CORS) Error while sending the POST request. Go to inspect element → console to know the error.
Access to fetch at 'https://oadnufagsd.execute-api.ap-south-1.amazonaws.com/dev' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
-
What caused the error ?: localhost which a different IP-address is trying to fetch https://oadnufagsd.execute-api.ap-south-1.amazonaws.com/dev which is at a different IP address. By default due to security concerns CORS is disabled which prevents information to share with other IPs or resources.
Solution: CORS policy has to enabled in order to share resource or in other words access a different IP address in our case.- Method-1: Through AWS Console
- Enabling CORS via AWS Console by going to API Gateway → aws-dev-ses-project → /mail/post → Actions → Select Enable CORS from dropdown
- Method-2: Serverless
- Through serverless.yml add
cors: true
under API method
- Through serverless.yml add
functions:
createContact:
handler: handler.createContact
events:
- http:
path: /mail
method: POST
cors: true
- Deploy the changes
sls deploy
and runcd nextjs-blog && npm run dev
- Confirming through gmail
Note: I dont know Node.js was helped by online session
Top comments (0)