使用Amplify×Lambda×SES发送电子邮件
Amplify是什么意思?
AWS Amplify是一个开发框架和工具链,用于高效开发Web和移动应用程序。它帮助创建和管理后端服务,生成前端代码,以及部署和托管应用程序。这使得开发人员能够专注于应用程序的构建和测试。
请查看官方网站以获取更多详细信息:
https://aws.amazon.com/jp/amplify/
想要做的事情
前提 tí)
・使用React和Typescript进行前端实现。
・已完成Amplify环境搭建和使用amplify add api创建GraphQL API。
・创建的源代码已存储在GitHub上,请随时查看!
https://github.com/gentarokai/sample-lambda-ses
实施 DynamoDB 的配置
schema.graphql的定义
执行 amplify add api 后,将 schema.graphql 更改为以下内容,并使用 amplify push 将其反映到远程。
input AMPLIFY {
globalAuthRule: AuthRule = { allow: public }
} # FOR TESTING ONLY!
type Reservation @model {
id: ID!
user: User @hasOne
date: String!
comments: String
}
type User @model {
id: ID!
name: String!
email: String!
}
启用DynamoDB的数据流进行监视
一旦在 schema.graphql 定义完成后,确认在管理控制台创建了预订表后,启用预订表的数据流。
上述操作已经使得可以通过流(Stream)来检测DynamoDB的变更。
使用 Amplify CLI 创建函数的实现 2
接下来,我们将创建一个由DynamoDB Stream事件触发的Lambda函数。
在lambda函数中,我们想要执行的处理步骤如下所示:
-
- 从DynamoDB Stream接收到的预订信息中提取用户ID。
-
- 根据用户ID从用户表中获取用户信息(使用Amplify自动生成的查询)。
-
- →授予使用Appsync API所需的权限!
-
- 使用SES的API向用户信息中的电子邮件地址发送邮件。
- →授予使用SES所需的权限!
前提 (Paraphrase: 先决条件)
在使用Amplify CLI设置通过DynamoDB的Stream触发Lambda时,有以下两个选项。
-
- 将其设为存储类别的一部分。
将其设定为针对通过@model指令创建的DynamoDB的函数。
这次我们将使用方法2来进行实现。详细信息请参阅官方网页。
https://docs.amplify.aws/cli/usage/lambda-triggers/#dynamodb-lambda-triggers
添加功能
使用Amplify CLI来添加Lambda函数。
amplify add function
↓ 对话答案示例
? Provide a friendly name for your resource to be used as a label for this category in the project: testtrigger (プロジェクト内で使用される名前)
? Provide the AWS Lambda function name: mytrigger (lambdaの関数名)
? Choose the runtime that you want to use: NodeJS (使用する言語)
? Choose the function template that you want to use:
Hello world function
CRUD function for Amazon DynamoDB table (Integration with Amazon API Gateway and Amazon DynamoDB)
Serverless express function (Integration with Amazon API Gateway)
❯ Lambda Trigger
选择活动
? What event source do you want to associate with Lambda trigger (Use arrow keys)
❯ Amazon DynamoDB Stream
Amazon Kinesis Stream
选择使用由@model创建的表格。
? Choose a DynamoDB event source option
> Use API category graphql @model backed DynamoDB table(s) in the current Amplify project
Use storage category DynamoDB table configured in the current Amplify project
Provide the ARN of DynamoDB stream directly
? Choose the graphql @model(s) (Press <space> to select, <a> to toggle all, <i> to invert selection)
❯● Reservation (スペースで選択して決定)
◯ User
✅ Available advanced settings:
- Resource access permissions
- Scheduled recurring invocation
- Lambda layers configuration
- Environment variables configuration
- Secret values configuration
? Do you want to configure advanced settings? **No**
? Do you want to edit the local lambda function now? **No**
✅ Successfully added resource sampleLambdaSes locally.
✅ Next steps:
Check out sample function code generated in <project-dir>/amplify/backend/function/sampleLambdaSes/src
"amplify function build" builds all of your functions currently in the project
"amplify mock function <functionName>" runs your function locally
To access AWS resources outside of this Amplify app, edit the /Users/kaigentarou/Desktop/react/sample-lambda-ses/amplify/backend/function/sampleLambdaSes/custom-policies.json
"amplify push" builds all of your local backend resources and provisions them in the cloud
"amplify publish" builds all of your local backend and front-end resources (if you added hosting category) and provisions them in the cloud
当上述的@model生成DynamoDB事件来触发lambda函数的准备工作已经完成。
如果能确认在项目文件夹/amplify/backend/function目录下已经创建了lambda文件,则说明准备工作已经成功。
如下用中文母语进行释义:
选项一:实施第三个SES设置。
您需要在电子邮件的发件人中注册经SES认证过的电子邮件地址或域名。请根据以下参考进行认证:
https://docs.aws.amazon.com/ja_jp/ses/latest/dg/creating-identities.html#just-verify-email-proc
:::message
请注意,在测试环境中,您还需要对接收地址进行认证!
实施第四项:lambda的实现.
在这里,我们将在”项目文件夹/amplify/backend/function/lambda函数名/src”路径下的index.js文件中编写实际的代码。
安装所需的库。
从项目文件夹移动到已安装Lambda函数的文件夹(上述文件夹),然后按照以下步骤进行操作。
我们将修改位于lambda下的package.json的内容为以下内容。
{
"name": "samplelambdases",
"version": "2.0.0",
"description": "Lambda function generated by Amplify",
"main": "index.js",
"license": "Apache-2.0",
"devDependencies": {
"@types/aws-lambda": "^8.10.92"
},
-----以下を追加------------------------------------------------
"dependencies": {
"@aws-sdk/client-s3": "^3.216.0",
"aws-sdk": "^2.1489.0",
"aws-appsync": "^4.1.9",
"graphql-tag": "^2.12.6"
}
-------------------------------------------------------------
}
在完成更改后,请使用以下命令安装库。
npm install
对GraphQL查询授予访问权限。
执行以下命令,更新设置以使lambda函数能够从Appsync的API中调用。
amplify update function
? Select the Lambda function you want to update.
> sampleLambdaSes (lambdaの関数名)
? Which setting do you want to update?
> Resource access permissions
? Select the categories you want this function to have access to.
> API
? Select the operations you want to permit on gql-api.
> Query
You can access the following resource attributes as environment variables from your Lambda function
API_SAMPLELAMBDASES_GRAPHQLAPIENDPOINTOUTPUT
API_SAMPLELAMBDASES_GRAPHQLAPIIDOUTPUT
API_SAMPLELAMBDASES_GRAPHQLAPIKEYOUTPUT
? Do you want to edit the local lambda function now? No
通过上述步骤,已经将从lambda到GraphQL的访问权限授予。
通过授予权限,以下值已自动设置为环境变量:
– API_API名_GRAPHQLAPIENDPOINTOUTPUT:GraphQL端点URL
– API_API名_GRAPHQLAPIIDOUTPUT:API的ID
– API_API名_GRAPHQLAPIKEYOUTPUT:API密钥
授予对SES的访问权限
请在 lambda 函数文件夹中的 <函数名称>-cloudformation-template.json 文件的”lambdaexecutionpolicy/Properties/PolicyDocument/Statement”中添加以下内容。
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
// ここから
{
"Effect": "Allow",
"Action": [
"ses:SendEmail",
"ses:SendRawEmail"
],
"Resource": "*"
},
// ここまで
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents" → この下に直接追加すると権限が付与されないので注意!
],
"Resource": {
.
.
.
}
添加环境变量
设定值
设置用于在lambda函数内使用的环境变量。
设定值如下:
-
- REGION: 您可以从项目文件夹/src下的aws-exports.js获取已安装应用的地区信息。
- EMAIL_FROM: 当使用经过认证的域名时,您需要创建任意的邮件地址,例如”no-reply@域名”。
设置步骤
按照以下步骤设置上述四个环境变量。
$ amplify update function
? Which setting do you want to update?
Resource access permissions
Scheduled recurring invocation
Lambda layers configuration
> Environment variables configuration
Secret values configuration
? Select what you want to do with environment variables:
> Add new environment variable
Update existing environment variables
Remove existing environment variables
I'm done
? Enter the environment variable name: REGION
? Enter the environment variable value: アプリのリージョン名
? Select what you want to do with environment variables:
❯ Add new environment variable
? Enter the environment variable name: EMAIL_FROM
? Enter the environment variable value: SESで認証済みのメールアドレスまたはドメイン(アドレス@ドメインの形式)
? Do you want to edit the local lambda function now? No
代码的实现
接下来,我们将修改index.js的代码。
代码的简要内容如下所示。
-
- 从 DynamoDB Stream 接收到的预订信息中提取用户ID
-
- 使用用户ID从用户表中获取用户信息(使用 Amplify 自动生成的查询)
- 使用 SES API 向用户信息中的电子邮件地址发送电子邮件
// ライブラリのインポート
const AWS = require("aws-sdk");
const gql = require("graphql-tag");
const AWSAppSyncClient = require("aws-appsync").default;
// ユーザーIDを使用してユーザー情報を取得するクエリ
// → src/graphql/queries.ts のクエリをコピーしてくる
const query = gql`
query GetUser($id: ID!) {
getUser(id: $id) {
id
name
email
createdAt
updatedAt
__typename
}
}
`;
// AppSyncのAPIを使用してユーザー情報を取得するメソッド
const getUserQuery = async (userId) => {
// Appsyncクライアントの作成
const client = new AWSAppSyncClient({
// 環境変数:GraphQLのエンドポイントURL(要変更!)
url: process.env.API_API名_GRAPHQLAPIENDPOINTOUTPUT,
region: process.env.REGION, // 環境変数: リージョン
auth: {
type: "API_KEY",
// 環境変数:API KEY(要変更!)
apiKey: process.env.API_API名_GRAPHQLAPIKEYOUTPUT,
},
disableOffline: true,
});
// Appsyncのクエリを実行
const graphqlData = await client.query({
query: query,
variables: { id: userId },
authMode: "API_KEY",
});
// ユーザー情報を返却
return graphqlData.data.getUser;
};
// メール送信ロジック
const sendEmail = async (params) => {
const ses = new AWS.SES();
try {
await ses.sendEmail(params).promise();
console.log("email sent");
} catch (err) {
console.log("error sending email", err);
}
};
// メインロジック
exports.handler = async (event) => {
for (const record of event.Records) {
if (record.eventName === "INSERT") {
// 予約情報からユーザーIDを取り出す
const reservation = record.dynamodb.NewImage;
const userId = reservation.reservationUserId.S;
// ユーザー情報を取得
const user = await getUserQuery(userId);
if (user) {
const email = user.email;
// メール内容の作成
const params = {
Destination: {
ToAddresses: [email],
},
Message: {
Body: {
Text: {
Data: `Dear ${user.name}, your reservation has been confirmed.`,
},
},
Subject: {
Data: "Reservation confirmed",
},
},
// 環境変数:メール送信元
Source: process.env.EMAIL_FROM,
};
// メール送信
return sendEmail(params);
}
}
}
};
使用 Tips amplify mock 对 Function 进行测试。
在创建Lambda代码时,每次都进行amplify push可能会花费很长时间,所以可以通过模拟API在本地进行函数测试。这还可以用于验证API的运行情况,如果您感兴趣,请参考以下文章。
https://docs.amplify.aws/cli/usage/mock/
确认结果
创建输入表单
本次我们将使用chakra UI和React Hook Form来创建一个简单的表单并进行验证。
安装npm库
在package.json的dependencies中注明所需的库,并通过npm install进行安装。
"dependencies": {
"@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"aws-amplify": "^5.3.11",
"aws-appsync": "^4.1.9",
"aws-sdk": "^2.1489.0",
"graphql": "15.3.0",
"graphql-tag": "^2.12.6",
"react-hook-form": "^7.47.0"
},
创建输入表单
请将项目文件夹中的App.tsx更改为以下内容:
::::细节 App.tsx
import {
Box,
Button,
ChakraProvider,
FormControl,
FormErrorMessage,
FormLabel,
Input,
Stack,
} from "@chakra-ui/react";
import { API, Amplify } from "aws-amplify";
import { createReservation, createUser } from "./graphql/mutations";
import { useForm } from "react-hook-form";
import { CreateReservationMutation, CreateUserMutation } from "./API";
import { GraphQLQuery } from "@aws-amplify/api";
import awsconfig from "./aws-exports";
Amplify.configure(awsconfig);
function App() {
// フォーム送信時の処理
const onSubmit = async (input: Inputs) => {
// ユーザーデータの登録
const res = await API.graphql<GraphQLQuery<CreateUserMutation>>({
query: createUser,
variables: {
input: {
name: input.name,
email: input.email,
},
},
authMode: "API_KEY",
});
if (res.data && res.data.createUser) {
// 予約データの登録
await API.graphql<GraphQLQuery<CreateReservationMutation>>({
query: createReservation,
variables: {
input: {
date: input.date,
comments: input.comments,
reservationUserId: res.data.createUser.id,
},
},
authMode: "API_KEY",
});
}
console.log(res);
};
type Inputs = {
name: string;
email: string;
date: string;
comments: string;
};
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<Inputs>();
return (
<ChakraProvider>
<Box w="50vh" marginX={50}>
<Stack spacing={3}>
{/* <form onSubmit={handleSubmit(onSubmit)}> */}
<form onSubmit={handleSubmit(onSubmit)}>
<FormControl>
<FormLabel htmlFor="name">First name</FormLabel>
<Input
id="name"
placeholder="name"
{...register("name", {
required: "This is required",
minLength: {
value: 4,
message: "Minimum length should be 4",
},
})}
/>
<FormErrorMessage>
{errors.name && errors.name.message}
</FormErrorMessage>
</FormControl>
<FormControl>
<FormLabel htmlFor="email">email</FormLabel>
<Input
id="email"
placeholder="email"
{...register("email", {
required: "This is required",
minLength: {
value: 4,
message: "Minimum length should be 4",
},
})}
/>
<FormErrorMessage>
{errors.email && errors.email.message}
</FormErrorMessage>
</FormControl>
<FormControl>
<FormLabel htmlFor="date">date</FormLabel>
<Input
id="date"
placeholder="date"
type="datetime-local"
{...register("date", {
required: "This is required",
minLength: {
value: 4,
message: "Minimum length should be 4",
},
})}
/>
<FormErrorMessage>
{errors.date && errors.date.message}
</FormErrorMessage>
</FormControl>
<FormControl>
<FormLabel htmlFor="comments">comments</FormLabel>
<Input
id="comments"
placeholder="comments"
{...register("comments", {
required: "This is required",
minLength: {
value: 4,
message: "Minimum length should be 4",
},
})}
/>
<FormErrorMessage>
{errors.comments && errors.comments.message}
</FormErrorMessage>
</FormControl>
<Button
mt={4}
colorScheme="teal"
isLoading={isSubmitting}
type="submit"
>
Submit
</Button>
</form>
</Stack>
</Box>
</ChakraProvider>
);
}
export default App;
使用以下命令运行测试。
npm run dev (npm start):::message
如果在使用Vite创建React应用程序时,运行npm run dev时屏幕上没有任何内容显示,请参考以下文章进行修正!
https://zenn.dev/fugithora812/articles/6dc080b48dc149
确认结果
接下来,我们将确认DynamoDB是否触发了Lambda函数的执行。
动作确认就是以上。非常感谢您长时间的陪伴!希望对您有所帮助!!