Skip to content

TiedWorkspace ご招待フォーム (invite-form)

利用規約を掲示して同意を取得し、設定済みの招待リンクをメールで配信するアプリ。 承認済みの候補者へワークスペース参加用リンクを届ける、招待フロー の 最終ステップを担う内製フォーム。

  • アプリ: apps/invite-app
  • Worker 名: invite-web (React Router v7 framework モード / SSR on Cloudflare Workers)
  • 公開 URL (予定): https://invite.tied-workspace.com

管理 UI は別アプリ

招待リンク・メール文面の設定や、同意・送信履歴の閲覧といった管理 UI は本アプリには含めず、 別アプリ tied-workspace-admin が担う。本アプリは認証を 持たない公開フォームに徹し、D1 の invite_settings読み取りのみ行う。

全体像

本アプリは未認証で開ける公開フォーム (/) のみを持つ。

mermaid
flowchart TD
    A["候補者: /(フォーム)"] --> B["利用規約を掲示"]
    B --> C{"同意して送信"}
    C -->|入力不備| B
    C -->|OK| T{"Turnstile 検証"}
    T -->|失敗| B
    T -->|OK| D["D1 invitations に同意を記録 (pending)"]
    D --> E["invite_settings の招待リンクを取得 (読み取り)"]
    E --> F["Cloudflare send_email で配信"]
    F -->|成功| G["invitations を sent に更新 → 完了画面"]
    F -->|失敗| H["invitations を failed に更新 → 再送案内"]
    I["tied-workspace-admin (別アプリ)"] -.->|招待リンク/文面を設定 (書き込み)| E
    I -.->|履歴閲覧| D

設計の不変条件

  • 公開フォームのみ・認証なし: 本アプリは候補者が未認証で開ける公開フォームに徹する。 管理 UI と認証 (better-auth) は持たず、別アプリ tied-workspace-admin に分離する。 したがって Cloudflare Access も使わない。
  • invite_settings は読み取り専用: 招待リンク・メール文面の設定 (書き込み) は tied-workspace-admin が行う。本アプリは設定を読むだけで、未設定ならフォームを受付停止にする。
  • 同意は記録してから配信する: 送信は invitationspending で作成し、メール送信の成否で sent / failed に更新する。失敗分も記録に残り、admin アプリ側で把握・再送できる。
  • 利用規約はコードが正本: 規約本文は apps/invite-app/app/lib/terms.ts に構造化して持つ。 改定したら TERMS_VERSION を更新する。同意記録 (invitations.terms_version) は同意時点の バージョンを保存し、どの版に同意したかを後から追跡できる。
  • メール送信は差し替え可能: 配信は EmailSender ポート (app/lib/email.server.ts) に 抽象化する。既定は Cloudflare Email Routing の send_email バインディング (cloudflare:email)。別プロバイダ (Resend 等) へはポートを差し替えるだけで移行できる。
  • 公開フォームはボット対策必須: 未認証でメール配信を起動できるため、送信時に Cloudflare Turnstile のトークンをサーバー側 (app/lib/turnstile.server.ts) で検証し、 ボット/スパムを弾く。検証に失敗したら同意記録もメール配信もしない。

アーキテクチャ

ロジックは app/lib/ にサーバー専用モジュールとして閉じ、ルート (loader / action) は app/lib/deps.server.ts 経由でのみそれらを呼ぶ。

apps/invite-app/
├── app/
│   ├── root.tsx                     # 共通レイアウト (公開・認証なし)
│   ├── routes.ts
│   ├── components/terms-view.tsx    # 利用規約の読み取り専用ビュー
│   ├── routes/index.tsx             # 公開フォーム (loader: 受付可否 / action: 同意送信)
│   └── lib/
│       ├── deps.server.ts           # ルートが使う依存の集約 / 組み立て
│       ├── terms.ts                 # 利用規約 (TERMS_VERSION / 条文)
│       ├── invitations.server.ts    # D1 データ層 (invite_settings 読取 / invitations 書込)
│       ├── email.server.ts          # EmailSender ポート + Cloudflare 実装
│       ├── turnstile.server.ts      # Cloudflare Turnstile トークン検証 (ボット対策)
│       └── invite-flow.server.ts    # 同意送信ユースケース (submitConsent)
└── workers/app.ts                   # Cloudflare Worker エントリ (fetch)

データモデル

D1 tied-workspace に 2 テーブル (スキーマは packages/d1/migrations/tied-workspace/0002_invite_form.sql)。 本アプリは invite_settings を読み取り、invitations に書き込む。invite_settings の設定は tied-workspace-admin が行う。

invite_settings (招待設定 / 単一行 id='default')

カラム説明
invite_url配信する招待リンク (例: Slack ワークスペース参加 URL)
email_subjectメール件名
email_body本文テンプレート。 / を差し込む
updated_at / updated_by最終更新時刻 / 更新者 (admin アプリが記録)

invitations (同意・送信記録 / 1 行 = 1 通)

カラム説明
name / emailフォーム入力
terms_version同意した利用規約のバージョン
statuspending / sent / failed
email_error送信失敗時のエラー要約
ip_address / user_agent同意取得時のメタ情報
created_at / sent_at受付時刻 / 送信時刻

招待フローとの関係

招待フロー の Step 5(アラートなしで招待手続き)で、候補者に本フォームを 案内する。候補者が同意して送信すると招待リンクが自動でメール配信される (従来の Notion フォーム + Notion オートメーションを内製アプリへ置き換えたもの)。配信する 招待リンクは事前に tied-workspace-admin で設定しておく。

運用

開発・デプロイ・環境変数・メール送信の設定は 運用 / デプロイ を参照。