n8nでGoogleカレンダー監視→Gmail自動送信!Camblyチケット消え防止ワークフローを全設定した話【PART2】

この記事にはアフィリエイト広告が含まれています(広告)

PART1では、Claudeエージェントを使ってCamblyのレッスン履歴をGmailで通知しようとしたら自動送信できないことが判明して、n8nで解決することにした話を書きました。

このようにキャンブリーで自分のチケットがあと何枚残ってるか計算して、損しそうならメールを自分に送るようにした完結編 PART2です。

今回はその「n8nで解決した」部分、つまりワークフローの全設定手順を書きます。

ワークフロー自体は6つのノードを繋ぐだけでそこまで複雑じゃないんですが、Google OAuthの認証設定がまえも同じ内容でつまったのんですよ、「あれ、なんかうまくいかない…」「このエラーなに?」「安全でないページって表示されてるけどこれ押していいの?( ゚д゚)」みたいなことが何回も起きて、いわゆる「認証の壁」に全力でぶつかった感じです笑

でもきちんとメモしてあってたすかりました。

一回突破すれば仕組みは動き続けてくれるので、同じ目に遭う方の参考になれば嬉しいです。


スポンサーリンク

まずワークフローの全体像を確認する

設定に入る前に、今回作ったワークフロー全体像を先に確認しておきましょう。

【ノード1】Schedule Trigger
毎週金曜 15:00 JST に自動起動
↓
【ノード2】Code(週の範囲計算)
今週の月曜3時JST〜来週の月曜3時JSTを計算
↓
【ノード3】Google Calendar(イベント取得)
期間内の「Cambly」イベントをすべて取得
↓
【ノード4】Code(件数カウント)
レッスン数カウント+メール文面を生成
↓
【ノード5】IF(3回未満かどうか判定)
↓ true(3回未満)の場合のみ
【ノード6】Gmail(メール送信)
リマインダーメールを自分宛に送信

スーツマンのレッスンプランは、30分を週3回としてるので、金曜の午後3時に自動で動き出して、Camblyのレッスンが週3回に満たなかったらメールが届く、という仕組みにしました。

ノード2で「月曜3時JST」を起点に週の期間を計算するところだけ少しコードを書く場面があります。そこ以外は基本的にノードを繋いで値を設定するだけなので、プログラミングが苦手でも大丈夫だと思います。

※本リンクはアフィリエイト広告です(PR)

📱 このワークフローを作った理由は「チケットを無駄にしてしまったから」です

Camblyのチケットは月曜3時JSTにリセットされます。使い切れないと無駄になるので、金曜に自動でリマインドされる仕組みを作ることにしました。チケットを無駄にしたくない方、試してみてください。それくらいしてまで利用したい英会話Camblyを試してみてください。プロモコード「startnowa」で最大38%オフ。

レッスン録画、翻訳機能付きチャットシステムなど、最先端のテクノロジーを駆使したアプリ!【Cambly(キャンブリー)】

またハマった!Google OAuth認証の設定

はい、またはまったんです。いつも???となる場面、でもCLAUDEでログをとっていたのですぐに思い出してもらうことができました。

n8nでGoogleカレンダーやGmailを使うには、Google Cloud ConsoleでOAuth2の認証情報を作る必要があります。

Google Cloud ConsoleでOAuth2の認証情報を作る

まずGoogle Cloud Consoleにアクセスします。

Google Cloud Console → 新しいプロジェクト作成 → APIとサービス → 認証情報 → 認証情報を作成 → OAuthクライアントID

アプリケーションの種類は「ウェブアプリケーション」を選びます。

ここで詰まりポイント①です。

承認済みのリダイレクトURIに、n8nのURLを入力する箇所があるんですが、IPアドレスを入れるとエラーになります。

「末尾はパブリック トップレベル ドメインにする必要があります」みたいな赤字のエラーが出てきて、「ええっなんで?」ってなりました(;´Д`A

GoogleのOAuth2はIPアドレス形式のURLを受け付けてくれないんですね。スーツマンはSynology NAS上でn8nを動かしていて、Tailscale(VPNツール)のMagicDNSホスト名をURLに使っているので、それを入力したら通りました。

https://(TailscaleのURL)/rest/oauth2-credential/callback

あとHTTPではなくHTTPSじゃないとダメです。そのためにTailscale Funnelを使っています。Funnelの設定はPART1に書きました。

Camblyチケット、また消えた。週の使い切り忘れをSynologyとn8nで防ぐ仕組みを作った話【PART1】

OAuth同意画面の設定

次にOAuth同意画面の設定をします。

APIとサービス → OAuth同意画面

ここで詰まりポイント②

最初テストモードで設定を進めていたら、認証しようとしたタイミングで「Access blocked: has not completed the Google verification process」というエラーが出ました。

Googleによる審査が必要なのかと思って焦ったんですが、実はテストユーザーに自分のGmailアドレスを追加するだけで解決します

UIがちょっとわかりにくくて、対象を選んでから「公開ステータス」の画面に入り、一番下にテストユーザーを追加する欄があります。ここに自分のGmailアドレスを入れればOKです。

「アプリを公開」ボタンが画面に大きく出てるんですが、審査プロセスが必要になるので最初は押さなくていいです。(ただしこの状態だと7日でトークンが切れる罠があって、後でその問題が出てくるんですが、それは後述します)

「安全でないページ」警告が出た――これ押していいの?

これが最大の落とし穴でした。

n8nのCredentials画面で「Sign in with Google」をクリックして認証フローを進めていくと、途中でこんな画面が出てきます。

「このページは安全ではありません」

「安全なページに戻る」か「詳細」→「(サイト名)にアクセスする(安全でないページ)」かを選ぶ2択です。

「安全なページに戻る」を押すと、認証が完了していないのにCredentialが保存されたように見えてしまいます。で、後でノードを実行したときに「Could not load list」「Unable to sign without access token」みたいなエラーが出てきて「あれ?なんで?」ってなるんです。。。。

正解は「詳細」→「アクセスする」を選ぶことです。

自分で作ったOAuthアプリはGoogleの審査を受けていないので、テストモードではこの警告が必ず出ます。でも自分が作ったアプリなので問題ありません。「続ける」を押すとGoogleカレンダーへのアクセス許可画面が出てくるので、許可すれば認証完了です。

これに気づくのに結構時間かかりました。。。。

GmailのCredentialも設定する

GoogleカレンダーとGmailは別々のCredential設定が必要です。

ただし、同じOAuth2クライアントIDとシークレットを使い回せます。Google Cloud ConsoleでGmail APIを追加で有効化して、n8nで新しいGmail Credentialを作成するときに同じクライアントIDとシークレットを入力すればOKです。

「安全でないページ」の警告が今度も出ますが、もう迷わず「続ける」で進みます笑


6つのノードの設定手順

認証の壁を乗り越えたら、あとはワークフローを組んでいくだけです。

ノード1:Schedule Trigger(毎週金曜15:00 JST)

Trigger RuleをCustomに変更して、Cron Expressionを入力します。

0 6 * * 5

これで「毎週金曜 06:00 UTC」、つまり日本時間の15:00に自動実行されます。

n8nのスケジュールはUTCで動くので、JSTとの時差(+9時間)を引いた時刻を入れる必要があります。最初これを知らなくて「なんで9時間ズレるんだろう?」ってなりました笑

ノード2:Code(今週の期間を計算)

Camblyの週は月曜日の3時JSTにリセットされます。なので「今週の月曜3時JST〜来週の月曜3時JST」の期間を計算して、次のGoogle Calendarノードに渡す必要があります。

コードはこんな感じです。

const jstOffset = 9 * 60 * 60 * 1000;
const nowUTC = new Date();
const nowJST = new Date(nowUTC.getTime() + jstOffset);
// Camblyの週は月曜3時JSTにリセットされる
const dayOfWeek = nowJST.getDay(); // 0=日, 1=月, ..., 5=金, 6=土
const daysFromMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
const lastMondayJST = new Date(nowJST);
lastMondayJST.setDate(nowJST.getDate() - daysFromMonday);
lastMondayJST.setHours(3, 0, 0, 0);
const nextMondayJST = new Date(lastMondayJST);
nextMondayJST.setDate(lastMondayJST.getDate() + 7);
// UTCに変換してAPIに渡す
const weekStart = new Date(lastMondayJST.getTime() - jstOffset);
const weekEnd   = new Date(nextMondayJST.getTime() - jstOffset);
return [{ json: {
  weekStart: weekStart.toISOString(),
  weekEnd:   weekEnd.toISOString()
}}];

getDay()が0(日曜)のときだけ6を引く形にしているのは、月曜を起点にするためです。金曜(5)なら「5-1=4日前が月曜」、土曜(6)なら「6-1=5日前が月曜」、日曜(0)だけ特別で「6日前が月曜」になります。

このコードが地味に詰まりました笑。最初「日曜起点」のコードを書いてしまっていて、Camblyのリセットが月曜3時だと気づくのに時間がかかりました。

ノード3:Google Calendar(イベントを取得)

  • Resource: Event
  • Operation: Get Many(←Get an Eventと迷いますが、期間指定で複数取るときはこっち)
  • Calendar: Primary
  • Return All: ON
  • After: {{ $json.weekStart }}
  • Before: {{ $json.weekEnd }}
  • Options → Add option → Query: Cambly

「Get an Event」だと特定のIDを指定して1件取得するのに対して、「Get Many Events」だと期間指定で複数取得できます。最初「Get an Event」を選んで「あれ、複数取れない!?」ってなりました( ゚д゚)

QueryにCamblyを入れることでカレンダー内のCamblyイベントだけに絞れます。

ノード4:Code(件数カウント+メール文面生成)

取得したイベントの数を数えてメール文面を作ります。

const events = $input.all();
const count = events.length;
const remaining = 3 - count;
return [{ json: {
  count,
  remaining,
  subject: `⚠️ 今週のCamblyチケットがあと${remaining}回分残っています`,
  body: `今週のCamblyレッスン受講数:${count}回\n\nあと${remaining}回分チケットが残っています。\n月曜3時JST(日曜深夜)がリセットなので、早めに予約しましょう!\n\n👉 https://www.cambly.com`
}}];

件数が少ない週ほど残り回数が多くなるので、remainingをメールタイトルに入れることで「あと何回使えるか」が一目でわかるようにしています。

ノード5:IF(3回未満かどうかを判定)

ここも地味に詰まりポイントがありました。

最新版のn8nだと、IFノードに「Operation」のドロップダウンがありません。条件行の左端に「T(テキスト型)」というアイコンがあって、そこをクリックしてNumberに変更してから「is less than」を選ぶ必要があります。

  • Value 1: {{ $json.count }}(※ 入力前に型をNumberに変更)
  • Operation: is less than
  • Value 2: 3

タイプをNumberにしないと数値の比較がうまく動きません。テキストとして比較されてしまい、IF判定がおかしくなります。これも最初わからなくてしばらく首をひねっていました笑

ノード6:Gmail(メール送信)

  • Operation: Send
  • To: 自分のGmailアドレス
  • Subject: {{ $json.subject }}
  • Message: {{ $json.body }}

これで「count < 3」のときだけメールが飛ぶようになります。


テスト実行してみた

全部設定したので実行ボタンを押してみました。

今週はすでに3回Camblyを受けていた週だったので、IF判定がfalseになってメール未送信でした。

「あれ動いてない?」って一瞬焦ったんですが、これが正しい動作です笑

3回達成してるから通知する必要ない、ということを正しく判断できてる!(´∀`)

翌週の金曜にちゃんとメールが届いたのを確認して、設定が完了しました╰(*´︶`*)╯


運用して7日後にトークンが切れた話

仕組みが動いた!と思ったら、7日後に突然メールが届かなくなりました。

n8nのExecutionsタブを確認したら「Get many events」ノードでこんなエラーが出ていました。

The provided authorization grant or refresh token is invalid, expired, revoked...

なんで!??( ゚д゚)

原因はOAuthアプリがテストモードのままだったことです。

GoogleのOAuth2アプリがテストモードの状態だと、リフレッシュトークンが7日で失効します。しかも再認証するにはまたTailscale Funnelを開いてHTTPS URLからアクセスしないといけない・・・・ヽ(´o`;

根本解決:OAuthアプリを「本番」に変える

1. Google Cloud Console → APIとサービス → OAuth同意画面

2. 「アプリを公開」ボタンを押す → 確認ダイアログ → 確認

3. Googleによる審査は不要(個人用アプリなので審査が通らない仕様。自分だけ使えればOK)

4. 変更後に一度だけ再認証すれば、以降トークンは無期限

「アプリを公開」というボタン名が「公開したら誰でも使えるようになる!?」という誤解を生む感じなんですが、個人用アプリは審査が通らないので実質的に自分以外は使えない。なので気にせず押しちゃいました。

再認証の手順(Tailscale Funnelが必要)

本番モードに変えた後、一度だけ再認証が必要です。ここがちょっとめんどくさい笑

1. SSHでSynologyに接続してFunnelを有効化する

sudo tailscale funnel --bg 5678

2. ブラウザでTailscaleのHTTPS URLを開く(ローカルIPではなくTailscaleのURL)

3. n8n → Credentials → 該当のCredential → Reconnect

4. 「安全でないページ」警告 → 「続ける」で認証

5. 完了後Funnelを閉じる

sudo tailscale funnel --https=443 off

ローカルIPでn8nを開いたまま再認証しようとすると {"status":"error","message":"Unauthorized"} が返ってきます。必ずTailscaleのHTTPS URLで開いた状態で再認証してください。これが再認証の最重要ポイントです。

本番モードにしてから再認証して、その後は7日経ってもずっと動き続けてくれています♪


まとめ

長くなりましたが、整理するとこんな感じです。

  • n8nのワークフロー自体は6つのノードを繋ぐだけでシンプル
  • つまづきやすい:Google OAuth認証設定
  • リダイレクトURIはIPアドレスNG → MagicDNSホスト名+HTTPS必須
  • 「安全でないページ」警告は「続ける」が正解(「戻る」を押すと認証未完了になる)
  • テストモードだと7日でトークン切れ → 「アプリを公開」で本番へ
  • Camblyの週の起点は月曜3時JST(ここ重要。日曜じゃないので注意)
  • 毎週金曜にメールが自動で届くようになってチケット消えが激減した

PART1でClaudeエージェントがGmail自動送信できなくて詰まった話→n8nで解決した話、という2部構成でした。Camblyを使っている方の参考になれば幸いです。英語学習者の同志のためにも、少しでもチケットを無駄にしないように、英語ペラペラになっていきましょう(´∀`)


今日使えそうな英語表現(B2〜C1)

英検1級を目指しているスーツマンが、この記事のテーマから使えそうな表現をAIにピックアップしてもらいました。

grant access(アクセス権を付与する) > “You’ll need to grant access to your Google Calendar when setting up the OAuth credentials.” OAuthの設定で「カレンダーへのアクセスを許可する」という場面そのままの表現です。”grant”は「正式に許可する・与える」のニュアンス。

token expiry / token expiration(トークンの有効期限切れ) > “Our automation broke after 7 days because of token expiry — we hadn’t switched from test mode.” “expiry”(英)と”expiration”(米)どちらでも通じます。IT系の文脈でよく出てくる表現です。

work around(〜を回避する、抜け道を見つける) > “We worked around the IP address restriction by using a MagicDNS hostname instead.” 「問題を正面から解決するのではなく迂回して対処する」ニュアンス。トラブルシューティングの話で頻繁に出てきます。


※本リンクはアフィリエイト広告です(PR)

📅 チケットを消したくないから自動化した、それがCamblyです

毎週自動でリマインドが来るくらいには使い続けているオンライン英会話です。ネイティブ講師と話すと、教科書には出てこない自然な言い回しをたくさん拾えます。プロモコード「startnowa」で最大38%オフ。アプリではなくウェブサイトから申し込めます。

※本リンクはアフィリエイト広告です(PR)

🖥️ n8nをVPSで動かしたい人へ

スーツマンはSynology NASでn8nをセルフホストしていますが、NASがない方にはConoHa VPSが選択肢になると思います。n8n用のスタートアップスクリプトが用意されていて、サーバー作成と同時に環境が整うのはかなり便利だと思います。

※本リンクはアフィリエイト広告です(PR)

🖥️ n8nをセルフホストするならSynology NASが選択肢になります

スーツマンもSynologyでn8nを動かしています。Dockerが動くモデルを選ぶのがポイントです。コスパ重視ならDS223、メモリ増設もしたいならDS225+が候補になります。なお本体のほかにHDD(別売り)が必要です。写真・動画の保存や外出先からのアクセスにも使えます。

▶ Synology DS223 をAmazonで見る(コスパ重視)

▶ Synology DS225+ をAmazonで見る(Docker安定動作重視)

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です