使用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函数中,我们想要执行的处理步骤如下所示:

    1. 从DynamoDB Stream接收到的预订信息中提取用户ID。

 

    1. 根据用户ID从用户表中获取用户信息(使用Amplify自动生成的查询)。

 

    1. →授予使用Appsync API所需的权限!

 

    1. 使用SES的API向用户信息中的电子邮件地址发送邮件。

 

    →授予使用SES所需的权限!

前提 (Paraphrase: 先决条件)

在使用Amplify CLI设置通过DynamoDB的Stream触发Lambda时,有以下两个选项。

    1. 将其设为存储类别的一部分。

将其设定为针对通过@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函数内使用的环境变量。
设定值如下:

    1. 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的代码。
代码的简要内容如下所示。

    1. 从 DynamoDB Stream 接收到的预订信息中提取用户ID

 

    1. 使用用户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函数的执行。

动作确认就是以上。非常感谢您长时间的陪伴!希望对您有所帮助!!

广告
将在 10 秒后关闭
bannerAds