[FastAPI] SESを使用し、ユーザー招待メールを送信し、ユーザー登録を成功させよう！！


# はじめに
本記事では、**FastAPI**を使用して、**PostgreSQL**にユーザー情報を保存し、**AWS SES（Simple Email Service）**を利用して招待メールを送信するシステムを構築する方法を解説します。システム設計から実装までを丁寧に説明し、初心者にも理解できるように構成しています。



# 必要な環境の準備

| 使用する技術スタック | 説明 |
|----------------------|------|
| **FastAPI**           | Pythonで書かれたWebフレームワーク。非同期処理に強く、軽量で高性能。 |
| **PostgreSQL**        | 信頼性の高いオープンソースのリレーショナルデータベース。 |
| **AWS SES**           | メール送信サービス。大規模なメール送信に対応。 |


#### 必要なパッケージ
まず、必要なパッケージをインストールします。

```bash
pip install fastapi uvicorn psycopg2 sqlalchemy boto3
```


| 使用する技術スタック | 説明 |
|----------------------|------|
| **FastAPI**           | APIの作成に使用。 |
| **uvicorn**           | ASGIサーバー。FastAPIを起動するために必要。 |
| **psycopg2**          | PostgreSQLとPythonを接続するためのライブラリ。 |
| **SQLAlchemy**        | ORM（Object Relational Mapper）としてデータベースとやり取り。 |
| **boto3**             | AWSサービス（SES）を操作するためのライブラリ。 |

#### PostgreSQLのセットアップ
PostgreSQLは事前にインストールしておく必要があります。以下のコマンドでデータベースを作成します。

```sql
CREATE DATABASE user_registration;
```



# データベース設計（PostgreSQL）

**ユーザー情報**と**招待トークン**を管理するために、2つのテーブルを設計します。

#### ユーザーテーブル（users）

| カラム名      | データ型      | 説明                              |
|---------------|---------------|-----------------------------------|
| **id**        | INTEGER       | ユーザーの一意のID（主キー）。    |
| **email**     | VARCHAR(255)  | ユーザーのメールアドレス（ユニーク）。|
| **name**      | VARCHAR(255)  | ユーザー名。                      |
| **is_active** | BOOLEAN       | アクティブかどうかを示すフラグ（招待を受けた後に有効化）。 |

```sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    name VARCHAR(255),
    is_active BOOLEAN DEFAULT FALSE
);
```

#### 招待トークンテーブル（invitation_tokens）

| カラム名      | データ型      | 説明                             |
|---------------|---------------|-----------------------------------|
| **id**        | INTEGER       | トークンの一意のID（主キー）。   |
| **email**     | VARCHAR(255)  | 招待されたメールアドレス。        |
| **token**     | VARCHAR(255)  | 招待トークン。                    |
| **is_used**   | BOOLEAN       | トークンが使用済みかどうかを示すフラグ。 |
```sql
CREATE TABLE invitation_tokens (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    token VARCHAR(255) NOT NULL,
    is_used BOOLEAN DEFAULT FALSE
);
```


# データベース接続設定

まずはSQLAlchemyを使ってデータベース接続を設定します。

```python
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base

DATABASE_URL = "postgresql://user:password@localhost/user_registration"

engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
```

#### ユーザーモデルとトークンモデル

SQLAlchemyでテーブルモデルを定義します。

```python
from sqlalchemy import Column, Integer, String, Boolean

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True, index=True)
    email = Column(String, unique=True, index=True)
    name = Column(String)
    is_active = Column(Boolean, default=False)

class InvitationToken(Base):
    __tablename__ = 'invitation_tokens'
    id = Column(Integer, primary_key=True, index=True)
    email = Column(String)
    token = Column(String)
    is_used = Column(Boolean, default=False)
```



# FastAPIのルート設計

APIは以下の2つに分離します。

1. **招待メール送信API**: ユーザーのメールアドレスを受け取り、AWS SESを使って招待メールを送信する
2. **ユーザー登録API**: 招待メールのリンクに含まれるトークンを使い、ユーザー登録を完了する



# 招待メール送信と新規ユーザー登録の流れ

**1. 招待メール送信の流れ**

このセクションでは、フロントエンドからユーザーのメールアドレスを送信し、バックエンドで処理して招待メールを送信する流れを説明します。

#### Step 1: フロントエンドからリクエストを送信
- フロントエンド（例：ReactやVue.jsなどのクライアントアプリケーション）で、ユーザーのメールアドレスを収集し、APIエンドポイント`/invite`にPOSTリクエストとして送信します。
- このリクエストには、ユーザーのメールアドレスが含まれます。

#### Step 2: FastAPIサーバーでトークン生成と保存
- サーバー側で、FastAPIはこのリクエストを受け取ります。
- FastAPIのエンドポイント`/invite`は、受け取ったメールアドレスに基づいて新しい招待トークンを生成します。トークンは通常、UUID形式を使用して一意の値を生成します。
  - 例: `token = str(uuid.uuid4())`
- このトークンとメールアドレスを**invitation_tokens**テーブルに保存します。保存されるデータには、以下の情報が含まれます:
  - メールアドレス
  - 招待トークン
  - トークンの使用ステータス（`is_used=False`）

#### Step 3: AWS SESでメール送信
- トークンが生成され、データベースに保存されたら、次にAWS SES（Simple Email Service）を使用して、ユーザーに招待メールを送信します。
- メールには、トークンを含んだリンクが記載されており、ユーザーがそのリンクをクリックすると登録画面に誘導されます。
  - 例: `https://example.com/register?token=<generated_token>`
- AWS SESは、Boto3ライブラリを通じてメールを送信します。


**2. 新規ユーザー登録の流れ**

招待を受け取ったユーザーが、登録を完了する流れです。

#### Step 1: 招待メールを受け取ったユーザーがリンクをクリック
- ユーザーは、送られた招待メール内のリンクをクリックします。このリンクには、トークンがクエリパラメータとして含まれています。
  - 例: `https://example.com/register?token=<generated_token>`
  
#### Step 2: トークンを持ってAPIエンドポイントにリクエストを送信
- ユーザーは、登録画面で名前やパスワードなどの必要情報を入力し、登録リクエストをバックエンドの`/register`エンドポイントに送信します。このリクエストには、招待トークンも含まれています。

#### Step 3: FastAPIでトークンを検証
- サーバー側では、受け取ったトークンをデータベース（**invitation_tokens**テーブル）で確認します。
- トークンが存在し、かつ未使用であることを確認します（`is_used=False`）。
- トークンが有効な場合、**users**テーブルにユーザー情報を保存し、ユーザー登録を完了します。
- 登録完了後、該当のトークンを使用済み（`is_used=True`）に更新します。


### このセクションのまとめ

**招待メール送信の流れ**
1. フロントエンドからユーザーのメールアドレスをAPIに送信。
2. FastAPIで招待トークンを生成し、データベースに保存。
3. AWS SESを使ってユーザーに招待メールを送信。

**新規ユーザー登録の流れ**
1. ユーザーがメール内のリンクをクリック。
2. トークンを使用してFastAPIにリクエストを送信。
3. FastAPIでトークンを検証し、ユーザーを登録。トークンを使用済みにする。

このようにFastAPIとPostgreSQL、SESを使ったシンプルかつセキュアなユーザー招待と登録システムが構築できます。


# AWS SESを用いたメール送信

AWS SESの設定はAWSコンソール上で行います。SESを使用するためには、ドメインの検証やメールアドレスの確認が必要です。  
boto3を使ってSESにメールを送信する関数を実装します。

**コード例（FastAPIエンドポイント `/invite` の処理）:**

```python
from fastapi import FastAPI, HTTPException, Depends
from sqlalchemy.orm import Session
from models import InvitationToken
import uuid
import boto3

app = FastAPI()

# データベースセッションの依存関係
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()


# SESの設定
ses = boto3.client('ses', region_name='us-west-2')

@app.post("/invite")
def send_invitation(email: str, db: Session = Depends(get_db)):
    # トークンの生成
    token = str(uuid.uuid4())

    # データベースに保存
    invitation_token = InvitationToken(email=email, token=token, is_used=False)
    db.add(invitation_token)
    db.commit()

    # 招待メールの送信
    send_email_with_ses(email, token)

    return {"message": "Invitation sent"}

def send_email_with_ses(email, token):
    # メールの内容を構築
    response = ses.send_email(
        Source='your-email@example.com',
        Destination={'ToAddresses': [email]},
        Message={
            'Subject': {'Data': 'You are invited to register'},
            'Body': {
                'Text': {
                    'Data': f"Click the link to register: https://example.com/register?token={token}"
                }
            }
        }
    )
    return response
```


この関数はSESを使って指定したメールアドレスに招待メールを送信します。

---



# ユーザー登録API

**コード例（FastAPIエンドポイント `/register` の処理）:**

```python
from fastapi import FastAPI, HTTPException, Depends
from sqlalchemy.orm import Session
from models import User, InvitationToken
from database import get_db

@app.post("/register")
def register_user(name: str, email: str, token: str, db: Session = Depends(get_db)):
    # トークンの検証
    invitation_token = db.query(InvitationToken).filter(InvitationToken.token == token).first()

    if not invitation_token or invitation_token.is_used:
        raise HTTPException(status_code=400, detail="Invalid or used token")

    # ユーザーの作成
    new_user = User(name=name, email=email, is_active=True)
    db.add(new_user)
    db.commit()

    # トークンを使用済みに更新
    invitation_token.is_used = True
    db.commit()

    return {"message": "User registered successfully"}
```


# まとめ

この記事では、FastAPI、PostgreSQL、AWS SESを使用して、ユーザー招待メールを送信し、ユーザーを登録するAPIの構築方法を解説しました。API設計を分かりやすくするため、機能ごとに分離して説明し、SESを用いたメール送信も丁寧に解説しました。