diff --git a/gmail-notion-forwarder-2026-06-25/.github/workflows/deploy-gas.yml b/gmail-notion-forwarder-2026-06-25/.github/workflows/deploy-gas.yml new file mode 100644 index 0000000..234611b --- /dev/null +++ b/gmail-notion-forwarder-2026-06-25/.github/workflows/deploy-gas.yml @@ -0,0 +1,72 @@ +name: Deploy Google Apps Script + +on: + push: + branches: + - main + paths: + - 'gmail-notion-forwarder-2026-06-25/gas/**' + - '.github/workflows/deploy-gas.yml' + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Install dependencies + working-directory: gmail-notion-forwarder-2026-06-25/gas + run: npm install + + - name: Install clasp + run: npm install -g @google/clasp + + - name: Create .clasp.json + working-directory: gmail-notion-forwarder-2026-06-25/gas + env: + CLASP_CONFIG: ${{ secrets.CLASP_CONFIG }} + run: echo "$CLASP_CONFIG" > .clasp.json + + - name: Authenticate with Google + env: + CLASP_CREDENTIALS: ${{ secrets.CLASP_CREDENTIALS }} + run: | + mkdir -p ~/.config/clasp + echo "$CLASP_CREDENTIALS" > ~/.config/clasp/credentials.json + + - name: Deploy to Apps Script + working-directory: gmail-notion-forwarder-2026-06-25/gas + run: clasp push + + - name: Create deployment + working-directory: gmail-notion-forwarder-2026-06-25/gas + run: clasp deployments + + - name: Notify Slack + if: always() + uses: slackapi/slack-github-action@v1.24.0 + with: + payload: | + { + "text": "GAS deployment: ${{ job.status }}", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Gmail to Notion Forwarder\nStatus: ${{ job.status }}\nCommit: ${{ github.sha }}" + } + } + ] + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK diff --git a/gmail-notion-forwarder-2026-06-25/.gitignore b/gmail-notion-forwarder-2026-06-25/.gitignore new file mode 100644 index 0000000..618c07d --- /dev/null +++ b/gmail-notion-forwarder-2026-06-25/.gitignore @@ -0,0 +1,28 @@ +# Node.js +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Terraform +*.tfstate +*.tfstate.* +.terraform/ +.terraform.lock.hcl +terraform.tfvars +!terraform.tfvars.example + +# clasp +.clasp.json +!.clasp.json.example + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# GAS +clasp_error_log.txt diff --git a/gmail-notion-forwarder-2026-06-25/README.md b/gmail-notion-forwarder-2026-06-25/README.md new file mode 100644 index 0000000..4e8e2ae --- /dev/null +++ b/gmail-notion-forwarder-2026-06-25/README.md @@ -0,0 +1,89 @@ +# Gmail to Notion Forwarder + +Gmail で受信したメールを Notion に自動転送するシステムです。 + +## 概要 + +- **実装**:Google Apps Script (GAS) +- **スケジュール**:時間ベースのトリガー(デフォルト:1時間ごと) +- **連携**:Gmail API + Notion API + +## セットアップ + +### 前提条件 + +- Google Workspace / Google Account +- Notion Workspace & API Token +- Node.js 18+ +- clasp CLI + +### 1. GAS プロジェクトのセットアップ + +```bash +cd gas +npm install +cp .clasp.json.example .clasp.json +# .clasp.json を編集してスクリプトID等を設定 +clasp push +``` + +### 2. Notion API キーの設定 + +Terraform で Google Secret Manager に保存します。 + +```bash +cd terraform +cp terraform.tfvars.example terraform.tfvars +# terraform.tfvars を編集 +terraform init +terraform apply +``` + +### 3. GAS でのトリガー設定 + +Google Apps Script エディタで以下を実行: +- メニュー:**実行 → 関数を実行 → forwardEmailsToNotion** +- トリガー:**時間ドリブン → 1時間ごと** + +または `appsscript.json` で事前定義 + +## ファイル構成 + +``` +. +├── gas/ # Google Apps Script +│ ├── main.gs # メイン処理 +│ ├── appsscript.json # トリガー定義 +│ ├── .clasp.json.example +│ └── README.md +├── terraform/ # IaC (秘密鍵管理) +│ ├── main.tf +│ ├── variables.tf +│ ├── terraform.tfvars.example +│ └── .gitignore +├── .github/workflows/ # CI/CD +│ └── deploy-gas.yml +└── README.md +``` + +## デプロイ + +GitHub Actions で自動デプロイ: +1. 変更を `gas/` にコミット +2. `main` ブランチにプッシュ +3. ワークフローが自動実行 + +## トラブルシューティング + +### メールが転送されない場合 +- Gmail ラベルの設定を確認 +- GAS の実行ログを確認:**実行 → ログ** +- Notion API キーの有効性を確認 + +### Notion API エラー +- API キーが正しく Secret Manager に保存されているか確認 +- Notion Database ID が正しいか確認 + +## ライセンス + +MIT diff --git a/gmail-notion-forwarder-2026-06-25/gas/.clasp.json.example b/gmail-notion-forwarder-2026-06-25/gas/.clasp.json.example new file mode 100644 index 0000000..622f21d --- /dev/null +++ b/gmail-notion-forwarder-2026-06-25/gas/.clasp.json.example @@ -0,0 +1,8 @@ +{ + "scriptId": "YOUR_SCRIPT_ID_HERE", + "projectIdSettings": { + "projectId": "YOUR_GCP_PROJECT_ID_HERE" + }, + "rootDir": "./", + "fileExtension": "gs" +} diff --git a/gmail-notion-forwarder-2026-06-25/gas/README.md b/gmail-notion-forwarder-2026-06-25/gas/README.md new file mode 100644 index 0000000..ff9cc54 --- /dev/null +++ b/gmail-notion-forwarder-2026-06-25/gas/README.md @@ -0,0 +1,109 @@ +# Google Apps Script Setup + +## セットアップ手順 + +### 1. Google Apps Script プロジェクトの作成 + +1. [Google Apps Script](https://script.google.com) にアクセス +2. 新しいプロジェクトを作成 +3. プロジェクト設定を開いて **Script ID** をコピー + +### 2. clasp ログイン + +```bash +npm install -g @google/clasp +clasp login +``` + +### 3. .clasp.json の設定 + +```bash +cp .clasp.json.example .clasp.json +``` + +`.clasp.json` を編集: +```json +{ + "scriptId": "YOUR_SCRIPT_ID_HERE", + "projectIdSettings": { + "projectId": "YOUR_GCP_PROJECT_ID_HERE" + } +} +``` + +### 4. スクリプトをデプロイ + +```bash +clasp push +``` + +### 5. 環境変数の設定(GAS UI) + +Google Apps Script エディタで: + +1. **プロジェクト設定 → スクリプト プロパティ** を開く +2. 以下を追加: + +| キー | 値 | +|------|-----| +| `NOTION_API_KEY` | Notion API キー | +| `NOTION_DATABASE_ID` | Notion Database ID | + +### 6. トリガーの設定 + +Google Apps Script エディタで: + +1. **実行 → トリガー(時計アイコン)** をクリック +2. **トリガーを追加** をクリック +3. 設定: + - 実行する関数:`forwardEmailsToNotion` + - 実行するデプロイ:`Head` + - イベントソース:`時間ベース` + - 時間トリガーのタイプ:`1 時間ごと` + +### 7. テスト + +Google Apps Script エディタで: + +1. **関数を選択 → testForwardEmail** +2. **実行(▶️ ボタン)** をクリック +3. **実行ログ** で結果を確認 + +## Notion API キーの取得 + +1. [Notion Integration Console](https://www.notion.so/my-integrations) にアクセス +2. **新しいインテグレーション** を作成 +3. 内部統合トークンをコピー +4. GAS スクリプト プロパティに設定 + +## Notion Database ID の取得 + +1. Notion でデータベースを開く +2. URL を確認:`https://www.notion.so/{DATABASE_ID}?v=...` +3. `{DATABASE_ID}` をコピー(32文字) + +## Properties(データベース構造) + +Notion Database に以下のプロパティを作成: + +| 名前 | タイプ | 説明 | +|------|--------|------| +| `Subject` | Title | メールの件名(必須) | +| `From` | Email | 送信元アドレス | +| `Received Date` | Date | 受信日時 | +| `Gmail Thread ID` | Text | Gmail スレッドID(検証用) | + +## トラブルシューティング + +### エラー: "Cannot read property 'getProperty' of null" +- スクリプト プロパティが設定されていないか確認 +- GAS UI で再度プロパティを追加 + +### エラー: "401 Unauthorized" +- Notion API キーが正しいか確認 +- トークンの有効期限を確認 + +### メールが転送されない +- Gmail ラベル「ToNotion」が正しく作成されているか確認 +- GAS **実行ログ** でエラーメッセージを確認 +- `testForwardEmail` で手動テストを実行 diff --git a/gmail-notion-forwarder-2026-06-25/gas/appsscript.json b/gmail-notion-forwarder-2026-06-25/gas/appsscript.json new file mode 100644 index 0000000..e42bc3e --- /dev/null +++ b/gmail-notion-forwarder-2026-06-25/gas/appsscript.json @@ -0,0 +1,21 @@ +{ + "timeZone": "Asia/Tokyo", + "exceptionLogging": "STACKDRIVER", + "runtimeVersion": "V8", + "dependencies": { + "enabledAdvancedServices": [ + { + "userSymbol": "Gmail", + "serviceId": "gmail", + "version": "v1" + } + ] + }, + "oauthScopes": [ + "https://www.googleapis.com/auth/gmail.modify", + "https://www.googleapis.com/auth/script.external_request" + ], + "executionApi": { + "access": "ANYONE" + } +} diff --git a/gmail-notion-forwarder-2026-06-25/gas/main.gs b/gmail-notion-forwarder-2026-06-25/gas/main.gs new file mode 100644 index 0000000..90b10c1 --- /dev/null +++ b/gmail-notion-forwarder-2026-06-25/gas/main.gs @@ -0,0 +1,163 @@ +// Gmail to Notion Forwarder +// Forwards emails from Gmail to Notion database + +const SCRIPT_PROPERTIES = PropertiesService.getScriptProperties(); + +// Notion API 設定 +const NOTION_API_KEY = SCRIPT_PROPERTIES.getProperty('NOTION_API_KEY'); +const NOTION_DATABASE_ID = SCRIPT_PROPERTIES.getProperty('NOTION_DATABASE_ID'); + +// Gmail ラベル(これを対象にメールを転送) +const GMAIL_LABEL = 'ToNotion'; + +/** + * Gmail のメールを Notion に転送する + */ +function forwardEmailsToNotion() { + try { + // 未処理のメールを取得 + const threads = GmailApp.search('label:' + GMAIL_LABEL + ' is:unread'); + + if (threads.length === 0) { + Logger.log('No unread emails found'); + return; + } + + Logger.log('Found ' + threads.length + ' unread email(s)'); + + threads.forEach(thread => { + const messages = thread.getMessages(); + + messages.forEach(message => { + const email = { + subject: message.getSubject(), + from: message.getFrom(), + date: message.getDate(), + body: message.getPlainBody(), + threadId: message.getThread().getId() + }; + + // Notion に追加 + createNotionPage(email); + + // メールに処理済みマークを付与 + message.star(); + thread.removeLabel(GmailApp.getUserLabelByName(GMAIL_LABEL)); + }); + }); + + Logger.log('Successfully forwarded emails to Notion'); + + } catch (error) { + Logger.log('Error: ' + error.toString()); + sendErrorNotification(error); + } +} + +/** + * Notion に新規ページを作成 + */ +function createNotionPage(email) { + const url = 'https://api.notion.com/v1/pages'; + + const payload = { + parent: { + database_id: NOTION_DATABASE_ID + }, + properties: { + 'Subject': { + title: [ + { + text: { + content: email.subject + } + } + ] + }, + 'From': { + email: email.from + }, + 'Received Date': { + date: { + start: new Date(email.date).toISOString().split('T')[0] + } + }, + 'Gmail Thread ID': { + rich_text: [ + { + text: { + content: email.threadId + } + } + ] + } + }, + children: [ + { + object: 'block', + type: 'paragraph', + paragraph: { + rich_text: [ + { + type: 'text', + text: { + content: email.body.substring(0, 2000) // 最初の2000文字 + } + } + ] + } + } + ] + }; + + const options = { + method: 'post', + headers: { + 'Authorization': 'Bearer ' + NOTION_API_KEY, + 'Notion-Version': '2022-06-28', + 'Content-Type': 'application/json' + }, + payload: JSON.stringify(payload), + muteHttpExceptions: true + }; + + const response = UrlFetchApp.fetch(url, options); + const result = JSON.parse(response.getContentText()); + + if (response.getResponseCode() !== 200) { + throw new Error('Notion API error: ' + JSON.stringify(result)); + } + + Logger.log('Created Notion page: ' + result.id); + return result.id; +} + +/** + * エラー通知を送信 + */ +function sendErrorNotification(error) { + // オプション:エラー通知をメール or Slack で送信 + Logger.log('Error notification would be sent here'); +} + +/** + * テスト用:特定のメールを手動で転送 + */ +function testForwardEmail() { + const threads = GmailApp.search('label:' + GMAIL_LABEL); + if (threads.length > 0) { + const message = threads[0].getMessages()[0]; + const email = { + subject: message.getSubject(), + from: message.getFrom(), + date: message.getDate(), + body: message.getPlainBody(), + threadId: message.getThread().getId() + }; + + Logger.log('Testing with email: ' + email.subject); + createNotionPage(email); + } else { + Logger.log('No emails found with label: ' + GMAIL_LABEL); + } +} diff --git a/gmail-notion-forwarder-2026-06-25/terraform/.gitignore b/gmail-notion-forwarder-2026-06-25/terraform/.gitignore new file mode 100644 index 0000000..d2dedce --- /dev/null +++ b/gmail-notion-forwarder-2026-06-25/terraform/.gitignore @@ -0,0 +1,16 @@ +# Terraform ファイル +*.tfstate +*.tfstate.* +.terraform/ +.terraform.lock.hcl + +# 秘密情報 +terraform.tfvars +!terraform.tfvars.example + +# ローカルファイル +.DS_Store +*.swp +*.swo +*~ +.idea/ diff --git a/gmail-notion-forwarder-2026-06-25/terraform/main.tf b/gmail-notion-forwarder-2026-06-25/terraform/main.tf new file mode 100644 index 0000000..35833e0 --- /dev/null +++ b/gmail-notion-forwarder-2026-06-25/terraform/main.tf @@ -0,0 +1,85 @@ +terraform { + required_version = ">= 1.0" + required_providers { + google = { + source = "hashicorp/google" + version = "~> 5.0" + } + } +} + +provider "google" { + project = var.gcp_project_id + region = var.gcp_region +} + +# Secret Manager API の有効化 +resource "google_project_service" "secretmanager" { + service = "secretmanager.googleapis.com" + disable_on_destroy = false +} + +# Notion API キーをシークレットとして保存 +resource "google_secret_manager_secret" "notion_api_key" { + secret_id = "gmail-notion-forwarder-notion-api-key" + + replication { + auto {} + } + + depends_on = [google_project_service.secretmanager] +} + +resource "google_secret_manager_secret_version" "notion_api_key" { + secret = google_secret_manager_secret.notion_api_key.id + secret_data = var.notion_api_key +} + +# Notion Database ID をシークレットとして保存 +resource "google_secret_manager_secret" "notion_database_id" { + secret_id = "gmail-notion-forwarder-notion-database-id" + + replication { + auto {} + } + + depends_on = [google_project_service.secretmanager] +} + +resource "google_secret_manager_secret_version" "notion_database_id" { + secret = google_secret_manager_secret.notion_database_id.id + secret_data = var.notion_database_id +} + +# サービスアカウント(オプション:Cloud Run などで使用する場合) +resource "google_service_account" "gas_runner" { + account_id = "gmail-notion-forwarder" + display_name = "Gmail to Notion Forwarder" +} + +# Secret Manager へのアクセス権を付与 +resource "google_secret_manager_secret_iam_member" "notion_api_key_access" { + secret_id = google_secret_manager_secret.notion_api_key.id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.gas_runner.email}" +} + +resource "google_secret_manager_secret_iam_member" "notion_database_id_access" { + secret_id = google_secret_manager_secret.notion_database_id.id + role = "roles/secretmanager.secretAccessor" + member = "serviceAccount:${google_service_account.gas_runner.email}" +} + +# 出力 +output "secret_manager_secret_ids" { + value = { + notion_api_key = google_secret_manager_secret.notion_api_key.id + notion_database_id = google_secret_manager_secret.notion_database_id.id + } + description = "Secret Manager に保存されたシークレットの ID" +} + +output "service_account_email" { + value = google_service_account.gas_runner.email + description = "サービスアカウントのメールアドレス" +} diff --git a/gmail-notion-forwarder-2026-06-25/terraform/terraform.tfvars.example b/gmail-notion-forwarder-2026-06-25/terraform/terraform.tfvars.example new file mode 100644 index 0000000..47bdb8c --- /dev/null +++ b/gmail-notion-forwarder-2026-06-25/terraform/terraform.tfvars.example @@ -0,0 +1,7 @@ +# GCP 設定 +gcp_project_id = "YOUR_GCP_PROJECT_ID" +gcp_region = "asia-northeast1" + +# Notion 設定 +notion_api_key = "secret_YOUR_NOTION_API_KEY" +notion_database_id = "YOUR_DATABASE_ID_32CHARS" diff --git a/gmail-notion-forwarder-2026-06-25/terraform/variables.tf b/gmail-notion-forwarder-2026-06-25/terraform/variables.tf new file mode 100644 index 0000000..cf68f79 --- /dev/null +++ b/gmail-notion-forwarder-2026-06-25/terraform/variables.tf @@ -0,0 +1,21 @@ +variable "gcp_project_id" { + type = string + description = "GCP プロジェクト ID" +} + +variable "gcp_region" { + type = string + description = "GCP リージョン" + default = "asia-northeast1" +} + +variable "notion_api_key" { + type = string + sensitive = true + description = "Notion API キー(内部統合トークン)" +} + +variable "notion_database_id" { + type = string + description = "Notion Database ID" +}