• #setup
  • #email
  • #stock-summary
  • #resend
  • #api
未分類

株式市場サマリーのメール送信設定ガイド

このドキュメントでは、/stock-summary スラッシュコマンドにメール送信機能を統合する手順を説明します。

📋 概要

実装フロー

アーキテクチャ

  • マークダウン生成: apps/web/content/stock-information/YYYY-MM-DD.md
  • HTML変換: マークダウン→HTML(テーブル・絵文字対応)
  • メール送信: Resend API経由でHTML形式メールを送信

🔧 セットアップ手順

1. Resend APIキーの取得

  1. Resend にログイン
  2. API Keys から新しいAPIキーを作成
  3. APIキーをコピー(例: re_123456789...

2. 環境変数の設定

.env ファイルに以下を追加:

# Resend API Configuration
RESEND_API_KEY=re_your_api_key_here
STOCK_SUMMARY_EMAIL_TO=[email protected]
STOCK_SUMMARY_EMAIL_FROM=[email protected]

⚠️ 注意: Resendの送信元メールアドレスは、ドメイン認証が必要です。

3. Resend パッケージのインストール

cd apps/web
pnpm add resend

4. メール送信API エンドポイントの作成

apps/web/server/api/send-stock-summary.post.ts を作成:

import { Resend } from 'resend'

export default defineEventHandler(async (event) => {
  const config = useRuntimeConfig()
  const body = await readBody(event)

  // Resend クライアント初期化
  const resend = new Resend(config.resendApiKey)

  try {
    const { data, error } = await resend.emails.send({
      from: config.stockSummaryEmailFrom,
      to: config.stockSummaryEmailTo,
      subject: `📊 米国株式市場サマリー - ${body.date}`,
      html: body.htmlContent,
    })

    if (error) {
      throw createError({
        statusCode: 500,
        message: `メール送信エラー: ${error.message}`,
      })
    }

    return {
      success: true,
      messageId: data?.id,
    }
  } catch (err) {
    console.error('Resend API Error:', err)
    throw createError({
      statusCode: 500,
      message: 'メール送信に失敗しました',
    })
  }
})

5. Nuxt設定の更新

apps/web/nuxt.config.ts に環境変数を追加:

export default defineNuxtConfig({
  runtimeConfig: {
    // サーバーサイドのみアクセス可能
    resendApiKey: process.env.RESEND_API_KEY,
    stockSummaryEmailFrom: process.env.STOCK_SUMMARY_EMAIL_FROM || '[email protected]',
    stockSummaryEmailTo: process.env.STOCK_SUMMARY_EMAIL_TO,
  },
})

6. マークダウン→HTML変換ユーティリティの作成

apps/web/server/utils/markdown-to-html.ts を作成:

import { marked } from 'marked'
import DOMPurify from 'isomorphic-dompurify'

/**
 * マークダウンをHTML(メール送信用)に変換
 */
export async function markdownToEmailHtml(markdown: string): Promise<string> {
  // マークダウンをHTMLに変換
  const rawHtml = await marked(markdown, {
    gfm: true, // GitHub Flavored Markdown
    breaks: true,
  })

  // XSS対策のサニタイズ
  const cleanHtml = DOMPurify.sanitize(rawHtml)

  // メール用スタイルを適用
  return `
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
      line-height: 1.6;
      color: #333;
      max-width: 800px;
      margin: 0 auto;
      padding: 20px;
      background-color: #f5f5f5;
    }
    .container {
      background-color: #ffffff;
      padding: 30px;
      border-radius: 8px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
    h1 {
      color: #1a1a1a;
      border-bottom: 3px solid #4CAF50;
      padding-bottom: 10px;
    }
    h2 {
      color: #2c3e50;
      margin-top: 30px;
      border-left: 4px solid #4CAF50;
      padding-left: 10px;
    }
    h3 {
      color: #34495e;
      margin-top: 20px;
    }
    table {
      width: 100%;
      border-collapse: collapse;
      margin: 20px 0;
      font-size: 14px;
    }
    table th {
      background-color: #4CAF50;
      color: white;
      padding: 12px;
      text-align: left;
      font-weight: 600;
    }
    table td {
      padding: 12px;
      border-bottom: 1px solid #ddd;
    }
    table tr:hover {
      background-color: #f5f5f5;
    }
    strong {
      color: #d32f2f;
    }
    .footer {
      margin-top: 40px;
      padding-top: 20px;
      border-top: 2px solid #eee;
      font-size: 12px;
      color: #888;
      text-align: center;
    }
  </style>
</head>
<body>
  <div class="container">
    ${cleanHtml}
    <div class="footer">
      <p>このメールは /stock-summary コマンドにより自動生成されました</p>
      <p>Generated at ${new Date().toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' })}</p>
    </div>
  </div>
</body>
</html>
  `
}

7. 必要なパッケージのインストール

cd apps/web
pnpm add marked isomorphic-dompurify
pnpm add -D @types/marked

📝 スラッシュコマンドの更新

.claude/commands/stock-summary.md を更新して、メール送信ステップを追加:

更新箇所

## 実行方法

**Task toolを使用してgeneral-purposeエージェントに以下を依頼:**

1. WebSearchで各銘柄の昨日の株価とニュースを検索
2. 市場全体の動向を検索
3. マクロ経済ニュースを検索
4. 収集した情報を統合してマークダウンレポートを作成
5. **マークダウンファイルを `apps/web/content/stock-information/YYYY-MM-DD.md` に保存**
6. **マークダウンをHTMLに変換**
7. **Bash toolで以下のコマンドを実行してメール送信:**

```bash
curl -X POST http://localhost:3000/api/send-stock-summary \
  -H "Content-Type: application/json" \
  -d '{
    "date": "YYYY-MM-DD",
    "htmlContent": "<html>...</html>"
  }'
  1. メール送信の成功/失敗を確認して報告

## 🧪 テスト手順

### 1. 開発サーバーの起動

```bash
cd apps/web
pnpm dev

2. 手動テスト(cURLで確認)

curl -X POST http://localhost:3000/api/send-stock-summary \
  -H "Content-Type: application/json" \
  -d '{
    "date": "2025-11-14",
    "htmlContent": "<h1>テストメール</h1><p>これはテストです</p>"
  }'

3. スラッシュコマンドでテスト

/stock-summary

🚀 本番環境へのデプロイ

Cloudflare Pagesの環境変数設定

  1. Cloudflare Pagesダッシュボードにログイン
  2. プロジェクトを選択 → SettingsEnvironment variables
  3. 以下の環境変数を追加:
    • RESEND_API_KEY: Resend APIキー
    • STOCK_SUMMARY_EMAIL_FROM: 送信元メールアドレス
    • STOCK_SUMMARY_EMAIL_TO: 送信先メールアドレス

デプロイ

cd apps/web
pnpm build
pnpm exec wrangler pages deploy

📊 メール送信のカスタマイズ

送信先を複数にする

apps/web/server/api/send-stock-summary.post.ts を修正:

to: [
  config.stockSummaryEmailTo,
  '[email protected]'
],

件名をカスタマイズ

subject: `📊 ${body.marketStatus} 米国株式市場サマリー - ${body.date}`,

添付ファイルを追加

await resend.emails.send({
  // ... 既存の設定
  attachments: [
    {
      filename: `stock-summary-${body.date}.pdf`,
      content: body.pdfContent, // Base64エンコードされたPDF
    }
  ],
})

🔍 トラブルシューティング

メールが届かない

  1. APIキーの確認: .env ファイルの RESEND_API_KEY が正しいか
  2. ドメイン認証: Resendでドメイン認証が完了しているか
  3. 送信制限: Resendの無料プランは月100通まで
  4. スパムフォルダ: 迷惑メールフォルダをチェック

エラー: "Domain not verified"

Resendでドメイン認証を行う必要があります:

  1. Resend → DomainsAdd Domain
  2. DNSレコード(TXT, MX, CNAME)を設定
  3. 認証完了まで待機(最大48時間)

開発環境でのテスト送信

.env.local で異なるメールアドレスを設定:

STOCK_SUMMARY_EMAIL_TO=[email protected]

📚 参考リンク

📋 レポートフォーマット仕様

レイアウト構成

  1. 保有銘柄パフォーマンス一覧 - テーブル形式で3銘柄を比較
  2. 市場全体の動向 - 主要指数、セクター別、市場心理指標
  3. 保有銘柄の動向(詳細) - 各銘柄の詳細分析
  4. マクロ経済ニュース - 重要度付きトピック一覧
  5. まとめ - 影響分析と今後の注目ポイント

主要ニュースの重要度マーク

各銘柄の主要ニュースには重要度マークを付与:

  • ⭐⭐⭐ 最重要(決算、大型契約、重大な業績変化、大型インサイダー取引)
  • ⭐⭐ 重要(アナリストレーティング変更、規制関連)
  • 参考情報(一般的なニュース)

どうでもいい情報は含めないこと。

市場心理指標

CNN Fear & Greed指数は数値(0-100)も含めて表記:

  • 0-25: 極度の恐怖
  • 26-45: 恐怖
  • 46-55: 中立
  • 56-75: 強欲
  • 76-100: 極度の強欲

例: CNN Fear & Greed | **22 (極度の恐怖)** | 悪化 | 投資家心理の著しい悪化

🎯 今後の拡張アイデア

  • メール送信履歴の保存(D1データベース)
  • 送信スケジュールの設定(Cloudflare Cron Triggers)
  • PDF添付ファイル生成
  • メールテンプレートのカスタマイズ機能
  • 複数の送信先グループ管理
  • Slack/Discord通知の統合
  • チャート画像の生成と埋め込み
  • 週次・月次サマリーの自動生成