Nexeed Lab

Dify×GmailをAPIで繋ぐ!メール自動返信AIの構築手順

Dify×GmailをAPIで繋ぐ!メール自動返信AIの構築手順

メール対応に毎日どれだけの時間を費やしていますか?問い合わせメールへの返信、社内からの定型的な質問対応、顧客からのFAQへの回答——これらを一人で抱えている方にとって、メール処理は「終わらない仕事」の代名詞になっているはずです。

この記事では、AIアプリケーション開発プラットフォームの DifyGmail API を組み合わせて、「受信メールを検知 → AI が返信文を生成 → 自動送信する」という一連のフローを構築する手順を解説します。ノーコードツールのような手軽さと、APIによる柔軟なカスタマイズ性を両立した実践的な構成です。

Pythonの基本的な読み書きができる方、Difyを使ったことがある方(または興味がある方)を対象にしています。ゼロから丁寧に説明するので、初めて試みる方もぜひ最後まで読んでみてください。


1. システム全体の構成を理解する

まず、今回構築するシステムの全体像を把握しましょう。

[Gmailの受信トレイ]
       ↓ Gmail API でポーリング(または Push通知)
[Pythonスクリプト / 中継サーバー]
       ↓ メール本文を Dify の API に送信
[Dify アプリケーション]
       ↓ LLM(Claude / GPT等)が返信文を生成
[Pythonスクリプト / 中継サーバー]
       ↓ Gmail API で返信メールを送信
[元のメール送信者]

キーとなるコンポーネントは以下の3つです。

コンポーネント 役割
Gmail API メールの受信検知・送信
Dify Chat/Completion API 受信メール内容を受け取り、返信文を生成
Pythonスクリプト 上記2つをつなぐ中継処理

Dify はノーコードで LLM アプリのロジックを組めるため、「どんな口調で返信するか」「FAQ情報をナレッジとして参照するか」といった AIの振る舞いを GUI で管理できます。一方、メールの送受信には Gmail API を使うことで、フィルタリングやラベル管理など Gmail 固有の機能もフル活用できます。


2. 事前準備:Gmail API の有効化とOAuth設定

Gmail API を利用するには、Google Cloud Console でプロジェクトを作成し、OAuth 2.0 の認証情報を取得する必要があります。

2-1. Google Cloud Console でのセットアップ

  1. Google Cloud Console にアクセスし、新規プロジェクトを作成します(例: dify-mail-bot)。
  2. 左メニューから「APIとサービス」→「ライブラリ」を開き、Gmail API を検索して「有効にする」をクリックします。
  3. 「APIとサービス」→「認証情報」→「認証情報を作成」→「OAuth クライアント ID」を選択します。
  4. アプリケーションの種類は「デスクトップアプリ」を選択し、作成します。
  5. 生成された credentials.json をダウンロードし、プロジェクトのルートディレクトリに配置します。

⚠️ credentials.json には機密情報が含まれます。.gitignore に追加し、絶対にリポジトリにコミットしないでください。

2-2. 必要なPythonライブラリのインストール

pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client requests python-dotenv

各ライブラリの役割:

  • google-auth 系: Gmail API の OAuth 認証処理
  • google-api-python-client: Gmail API クライアント
  • requests: Dify の REST API への HTTP リクエスト
  • python-dotenv: 環境変数の管理(API キーの安全な管理)

2-3. 初回認証トークンの取得

以下のスクリプトを実行すると、ブラウザが開いて Google アカウントの認証を求められます。認証後、token.json がローカルに保存され、以降はこのトークンで自動的に認証が行われます。

# auth_setup.py
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
import os

SCOPES = [
    'https://www.googleapis.com/auth/gmail.readonly',
    'https://www.googleapis.com/auth/gmail.send',
    'https://www.googleapis.com/auth/gmail.modify'
]

def get_credentials():
    creds = None
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file('credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        with open('token.json', 'w') as token:
            token.write(creds.to_json())
    return creds

if __name__ == '__main__':
    creds = get_credentials()
    print("認証成功!token.json が保存されました。")

このスクリプトを最初に一度実行しておくことで、以降のスクリプトは token.json を使って自動認証できます。


3. Dify でメール返信用AIアプリを作成する

次に、受信メールの内容を受け取って返信文を生成する Dify アプリケーションを作成します。

3-1. Dify アプリの作成

Dify にログインし、以下の手順でアプリを作成します。

  1. ダッシュボードから「アプリを作成」をクリックします。
  2. アプリタイプとして「テキストジェネレーター」(または「チャットボット」)を選択します。今回は1つのメールに対して1つの返信を生成するシンプルな処理なので、テキストジェネレーターが適しています。
  3. アプリ名を「メール自動返信Bot」などに設定します。

3-2. システムプロンプトの設定

Dify のプロンプト設定画面で、AIの振る舞いを定義するシステムプロンプトを入力します。以下は問い合わせ対応を想定したサンプルです。

あなたは株式会社〇〇のカスタマーサポート担当AIです。
受信した問い合わせメールに対して、以下のルールに従って返信文を作成してください。

【返信ルール】
- 丁寧で親しみやすいビジネス文体を使う
- 挨拶文(「お世話になっております」等)を冒頭に入れる
- 問い合わせ内容を1〜2文で要約してから回答する
- 回答が不明な場合は「担当者より改めてご連絡します」と記載する
- 返信文のみを出力し、説明や前置きは不要
- 署名は「カスタマーサポートチーム」で統一する

【入力形式】
差出人: {{sender}}
件名: {{subject}}
本文:
{{body}}

{{sender}}{{subject}}{{body}} は Dify の変数機能を使った入力パラメータです。API 経由でこれらの値を渡すことで、動的なプロンプト生成が実現します。

3-3. ナレッジベースの追加(オプション)

FAQ集や製品マニュアルを Dify のナレッジ機能にアップロードしておくと、AI が参照しながら回答を生成できます。

  1. 「ナレッジ」メニューから新規ナレッジを作成します。
  2. PDF、TXT、Markdown 形式でドキュメントをアップロードします。
  3. 作成したアプリの「コンテキスト」設定で、このナレッジを追加します。

これにより、単純な LLM の知識だけでなく、社内固有の情報を元にした返信が生成できるようになります。

3-4. API キーの取得

Dify のアプリ設定から「API アクセス」を開き、APIキーを生成してください。このキーは後ほど Python スクリプトで使用します。また、API エンドポイント URL も控えておきます(通常は https://api.dify.ai/v1 ですが、セルフホスト環境では異なります)。


4. 中継Pythonスクリプトの実装

Gmail API で未読メールを取得し、Dify API で返信文を生成し、Gmail API で送信するメインスクリプトを実装します。

4-1. 環境変数の設定

まず、.env ファイルに API キー等を記載します。

# .env
DIFY_API_KEY=app-xxxxxxxxxxxxxxxxxxxxxxxx
DIFY_API_URL=https://api.dify.ai/v1
TARGET_LABEL=INBOX
CHECK_INTERVAL_SECONDS=60

4-2. メインスクリプトの実装

# mail_bot.py
import os
import time
import base64
import email
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from googleapiclient.discovery import build
import requests
from dotenv import load_dotenv
from auth_setup import get_credentials

load_dotenv()

DIFY_API_KEY = os.getenv('DIFY_API_KEY')
DIFY_API_URL = os.getenv('DIFY_API_URL', 'https://api.dify.ai/v1')
CHECK_INTERVAL = int(os.getenv('CHECK_INTERVAL_SECONDS', 60))


def get_gmail_service():
    """Gmail APIサービスを取得する"""
    creds = get_credentials()
    return build('gmail', 'v1', credentials=creds)


def get_unread_messages(service):
    """未読かつラベル未処理のメールを取得する"""
    result = service.users().messages().list(
        userId='me',
        q='is:unread -label:ai-replied',  # AI返信済みラベルがないもの
        maxResults=10
    ).execute()
    return result.get('messages', [])


def parse_message(service, msg_id):
    """メールIDからメール情報を抽出する"""
    msg = service.users().messages().get(
        userId='me', id=msg_id, format='full'
    ).execute()

    headers = {h['name']: h['value'] for h in msg['payload']['headers']}
    sender = headers.get('From', '')
    subject = headers.get('Subject', '(件名なし)')
    thread_id = msg.get('threadId', '')

    # 本文の取得(マルチパート対応)
    body = ''
    payload = msg['payload']
    if 'parts' in payload:
        for part in payload['parts']:
            if part['mimeType'] == 'text/plain':
                data = part['body'].get('data', '')
                body = base64.urlsafe_b64decode(data).decode('utf-8')
                break
    elif 'body' in payload:
        data = payload['body'].get('data', '')
        if data:
            body = base64.urlsafe_b64decode(data).decode('utf-8')

    return {
        'id': msg_id,
        'thread_id': thread_id,
        'sender': sender,
        'subject': subject,
        'body': body[:2000]  # 長すぎる場合は先頭2000文字に制限
    }


def generate_reply_with_dify(sender, subject, body):
    """Dify APIを呼び出して返信文を生成する"""
    url = f"{DIFY_API_URL}/completion-messages"
    headers = {
        'Authorization': f'Bearer {DIFY_API_KEY}',
        'Content-Type': 'application/json'
    }
    payload = {
        'inputs': {
            'sender': sender,
            'subject': subject,
            'body': body
        },
        'response_mode': 'blocking',
        'user': 'mail-bot'
    }

    response = requests.post(url, json=payload, headers=headers, timeout=30)
    response.raise_for_status()
    result = response.json()
    return result.get('answer', result.get('text', ''))


def send_reply(service, thread_id, to_address, subject, reply_body):
    """返信メールを送信する"""
    message = MIMEMultipart()
    message['To'] = to_address
    message['Subject'] = f"Re: {subject}" if not subject.startswith('Re:') else subject
    message.attach(MIMEText(reply_body, 'plain', 'utf-8'))

    raw = base64.urlsafe_b64encode(message.as_bytes()).decode('utf-8')
    send_body = {'raw': raw, 'threadId': thread_id}

    service.users().messages().send(userId='me', body=send_body).execute()
    print(f"返信送信完了: {to_address}")


def mark_as_replied(service, msg_id):
    """処理済みラベルを付与し既読にする"""
    # 「ai-replied」ラベルを事前にGmailで作成しておく必要があります
    # ラベルIDの取得
    labels = service.users().labels().list(userId='me').execute()
    ai_replied_label_id = None
    for label in labels.get('labels', []):
        if label['name'] == 'ai-replied':
            ai_replied_label_id = label['id']
            break

    modify_body = {'removeLabelIds': ['UNREAD']}
    if ai_replied_label_id:
        modify_body['addLabelIds'] = [ai_replied_label_id]

    service.users().messages().modify(
        userId='me', id=msg_id, body=modify_body
    ).execute()


def main():
    """メインのポーリングループ"""
    print("メール自動返信Botを起動しました。")
    service = get_gmail_service()

    while True:
        try:
            messages = get_unread_messages(service)
            if messages:
                print(f"{len(messages)}件の未処理メールを検出しました。")
                for msg in messages:
                    mail_data = parse_message(service, msg['id'])
                    print(f"処理中: {mail_data['subject']} from {mail_data['sender']}")

                    reply_text = generate_reply_with_dify(
                        mail_data['sender'],
                        mail_data['subject'],
                        mail_data['body']
                    )

                    send_reply(
                        service,
                        mail_data['thread_id'],
                        mail_data['sender'],
                        mail_data['subject'],
                        reply_text
                    )

                    mark_as_replied(service, mail_data['id'])
                    time.sleep(2)  # API レート制限への配慮
            else:
                print("未処理メールなし。")

        except Exception as e:
            print(f"エラーが発生しました: {e}")

        print(f"{CHECK_INTERVAL}秒後に再チェックします...")
        time.sleep(CHECK_INTERVAL)


if __name__ == '__main__':
    main()

このスクリプトの主なポイントを整理します。

  • ポーリング方式: 指定秒数ごとに未読メールをチェックします。リアルタイム性を高めたい場合は、Gmail の Push通知(Pub/Sub) を利用する方法もあります。
  • 重複送信の防止: is:unread -label:ai-replied というクエリで、AI返信済みのメールを除外しています。Gmail に ai-replied というラベルを事前に作成しておく必要があります。
  • 本文の長さ制限: メール本文が長すぎると Dify(および LLM)のトークン制限に引っかかるため、先頭 2000 文字に制限しています。

5. 動作確認とチューニング

5-1. テスト実行

スクリプトを実行する前に、Gmail のフィルタ設定で自分専用のテスト用ラベル(例: test-ai-reply)を作成し、テストメールを特定のアドレスに送ることをおすすめします。いきなり本番メールで動作確認するのはリスクがあります。

# まず認証セットアップ(初回のみ)
python auth_setup.py

# メインスクリプトの実行
python mail_bot.py

正常に動作すると、以下のようなログが出力されます。

メール自動返信Botを起動しました。
1件の未処理メールを検出しました。
処理中: 製品について問い合わせです from test@example.com
返信送信完了: test@example.com
60秒後に再チェックします...

5-2. Dify プロンプトの改善

実際に返信文を確認しながら、Dify のプロンプトを調整します。よくある改善ポイントは以下の通りです。

課題 対処法
返信が長すぎる プロンプトに「200文字以内で返信してください」を追加
敬語のレベルが合わない 「〜でございます」「〜です・ます」等、文体を明示する
署名フォーマットが崩れる 署名テンプレートを具体的に記載する
英語のメールに日本語で返信してしまう 「受信メールと同じ言語で返信してください」を追加

5-3. エラーハンドリングの強化

本番運用では、以下の追加対策を検討してください。

# より堅牢なエラーハンドリングの例
import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('mail_bot.log'),
        logging.StreamHandler()
    ]
)

# Dify API タイムアウト時のリトライ処理
from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=4, max=10))
def generate_reply_with_dify_retry(sender, subject, body):
    return generate_reply_with_dify(sender, subject, body)

tenacity ライブラリを使うことで、API 呼び出しの失敗時に自動リトライが行われます(pip install tenacity でインストール)。


6. セキュリティと運用上の注意点

送信前の人間レビューを挟む設計も検討する

完全自動送信はリスクを伴います。特に導入初期は、「下書き保存モード」として動作させることをおすすめします。Gmail API では、send の代わりに drafts.create を使うことでメールを下書きとして保存できます。

def save_as_draft(service, thread_id, to_address, subject, reply_body):
    """返信を下書きとして保存する(人間レビュー用)"""
    message = MIMEMultipart()
    message['To'] = to_address
    message['Subject'] = f"Re: {subject}"
    message.attach(MIMEText(reply_body, 'plain', 'utf-8'))

    raw = base64.urlsafe_b64encode(message.as_bytes()).decode('utf-8')
    draft_body = {
        'message': {
            'raw': raw,
            'threadId': thread_id
        }
    }
    draft = service.users().drafts().create(userId='me', body=draft_body).execute()
    print(f"下書き保存完了: Draft ID = {draft['id']}")
    return draft['id']

この方法であれば、AIが生成した返信文をまず人間がチェックし、問題なければ「送信」をクリックするという安全なワークフローが実現します。

その他のセキュリティ注意事項

  • OAuth スコープは最小限に: 今回使用した gmail.readonlygmail.sendgmail.modify で十分です。gmail.modify すら不要な場合は削除してください。
  • メールの本文をログに残さない: 個人情報保護の観点から、メール本文そのものをログファイルに記録しないよう注意してください。
  • レート制限の考慮: Gmail API には1日あたりの送信上限(1ユーザーあたり約500通/日)があります。大量のメールを処理する場合は上限に注意してください。

まとめ

この記事では、Dify と Gmail API を組み合わせたメール自動返信AIシステムの構築手順を解説しました。

実装した主な内容:

  1. Google Cloud Console での Gmail API 有効化と OAuth 設定
  2. Dify でのメール返信用テキストジェネレーターアプリ作成
  3. Gmail API と Dify API を繋ぐ Python スクリプトの実装
  4. 重複送信防止・エラーハンドリング・下書きモードの実装

このシステムの最大の魅力は、AI の振る舞いを Dify の GUI で簡単に変更できる点です。プロンプトの調整、ナレッジベースの更新、使用するモデルの変更などを、コードに触れることなく行えます。

次のステップとして試してほしいこと:

  • Dify のワークフロー機能を使って、メールの種類(問い合わせ/クレーム/注文等)を分類し、分岐した返信ロジックを実装する
  • Gmail のフィルタ機能と組み合わせて、特定の送信者や件名のメールだけを処理対象にする
  • Slack 通知を追加して、AI が返信したメールの件名と相手をチームに共有する
  • スクリプトを Cloud RunAWS Lambda にデプロイして、常時稼働化する

メール対応の自動化は、一度仕組みを作ってしまえば継続的に時間を節約できる投資です。ぜひ自分のユースケースに合わせてカスタマイズしてみてください。


参考資料

この記事をシェア

XFacebookはてブ