部落格

不定期分享最新資訊文章

  • article-每日定時檢查未交作業並發送提醒信 | (EP.5) n8n 自動化講師應用教學

    2026/5/4

    AI自動化 自動化講師應用 工作流
    每日定時檢查未交作業並發送提醒信 | (EP.5) n8n 自動化講師應用教學
    哈囉,歡迎來到自動化工作流的實戰教學。在接續上一集建立作業提交表單後,今天要進入更關鍵的一環:每天自動比對誰還沒交作業,並寄出精準的提醒 Email。 身為講師,你應該把時間花在教學上,而不是每天手動逐一核對學生的繳交進度。這套工作流讓系統在每天早上 9 點幫你做完這件事,而且內建多重防呆機制,保證不會重複騷擾已繳交或當天已提醒過的學生。 工作流整體架構這套工作流圍繞著四份 Google Sheets 試算表運作,透過「資料比對」找出需要提醒的學生,再執行寄信與紀錄。整體分為以下五個階段: 階段 說明 1. 定時觸發 每天早上 9 點自動啟動 2. 讀取資料 同步抓取四份試算表資料 3. 交叉比對 找出未交且今日未提醒的學生 4. 寄送提醒 發送個人化提醒 Email 5. 寫入日誌 成功、失敗分別記錄於不同表格 Google Sheets 資料表設計在建立工作流前,需先在同一份試算表中建立四個工作表(Sheet),各自負責不同功能: assignments(作業清單) 欄位 說明 assignment_id 作業代號(唯一識別碼) assignment_name 作業名稱(顯示於信件中) due_at 截止時間 is_active 是否啟用(填入 1 代表啟用,0 代表停用) students(學生名單) 欄位 說明 student_id 學生代號 student_name 學生姓名 email 學生 Email submissions(繳交紀錄) 欄位 說明 student_id 繳交學生的代號 assignment_id 對應的作業代號 reminder_logs(提醒日誌) 欄位 說明 student_id 被提醒的學生代號 assignment_id 對應的作業代號 reminded_at 提醒時間(台灣時區) 設計原則: 工作流只讀取 is_active = 1 的作業,因此新增或停用作業只需修改試算表中的欄位值,完全不需要改動 n8n 流程。 核心比對邏輯:誰需要收到提醒信?工作流的核心是一段 JavaScript Code 節點,邏輯如下: 篩選條件:同時符合以下三點,才會被列入寄信名單 尚未繳交該份作業:比對 submissions 表,以 student_id + assignment_id 作為組合鍵判斷。 今天尚未被提醒過:比對 reminder_logs 表,確認今日(台灣時間)尚無對應紀錄。 有有效的 Email:缺少 Email 的學生資料直接略過,不中斷流程。 時區處理細節: 系統使用 UTC+8 台灣時間進行日期判斷,確保在台灣時間 9 點執行時,「今日」的判定是正確的。 123台灣時間 = UTC 時間 + 8 小時比對鍵 = student_id :: assignment_id今日判定 = taiwanNow.toISOString().slice(0, 10) 這個設計讓流程即使被手動觸發多次,同一位學生在同一天內也只會收到一封提醒信。 寄信節點設定與防錯機制信件內容動態化提醒信的主旨與內文都使用動態變數,讓每封信看起來更有針對性: 主旨: 作業提醒:[作業名稱] 尚未提交 內文: 包含學生姓名、作業代號、作業名稱、截止時間 自動重試機制寄信節點設定了「失敗自動重試」: 重試次數:3 次 每次間隔:5 秒 允許失敗繼續執行(continueOnFail: true)——確保單一學生的寄信失敗不會中斷整批發送 結果分流寄信完成後,系統透過判斷 Gmail 是否回傳 id 欄位,來決定寫入哪份日誌: 有 id(成功) → 寫入 reminder_logs,作為下次「今日已提醒」的比對依據 無 id(失敗) → 寫入 failed_reminder_logs,記錄錯誤訊息供人工追蹤 工作流測試清單正式上線前,建議準備以下測試情境逐一驗證: 學生已繳交: 確認不出現在寄信名單中 學生未繳交: 確認收到提醒信,且 reminder_logs 有新增紀錄 重複手動執行: 確認同一位學生當天只收到一封 學生無 Email: 確認流程不中斷,直接略過該筆資料 作業 is_active = 0: 確認該作業不在本次提醒範圍內 全數通過後,Cron 排程即可正式上線,系統從此每天早上自動幫你追蹤進度。 常見問答 (FAQ)Q:如何新增一份新作業,讓系統開始追蹤?A:直接在 Google Sheets 的 assignments 工作表中新增一列,填入 assignment_id、assignment_name、due_at,並將 is_active 設為 1 即可。工作流每天執行時會自動讀取最新資料,完全不需要修改 n8n 流程。 Q:如果某份作業的提醒期限已過,該如何停止寄送?A:將該作業在 assignments 表中的 is_active 欄位改為 0(或任何非 1 的值),工作流便不會再讀取這份作業,自然也不會繼續發送提醒。 Q:如何避免同一天對同一位學生重複寄送提醒信?A:工作流在執行比對時,會讀取 reminder_logs 中當日的紀錄,建立「今日已提醒組合鍵集合」(以 student_id + assignment_id 為鍵)。若該組合鍵已存在,這位學生就會從本次寄信名單中被剔除。這個機制確保即使工作流被手動觸發多次,同一學生每天最多只收到一封提醒。 Q:如果學生名單中某位學生沒有填寫 Email,會發生什麼事?A:Code 節點在組建寄信名單時,會直接跳過沒有 Email 的學生資料,不會將其加入輸出清單。即使有漏網之魚進入後續節點,發信前還設有一道「If Has Email」條件判斷,確保沒有 Email 的資料不會觸發寄信動作,也不會造成流程中斷。 Q:寄信失敗時,系統會怎麼處理?A:寄信節點設有「失敗自動重試 3 次,每次間隔 5 秒」的機制。若三次重試後仍失敗,系統會將該筆資料(含學生代號、作業代號、錯誤訊息、失敗時間)寫入 failed_reminder_logs 工作表,供管理員後續追查或手動補寄。此外,寄信節點設定了 continueOnFail: true,單一筆失敗不會中斷整批發送,其他學生仍會正常收到提醒。 Q:系統如何判斷學生是否已繳交?提交記錄需要手動維護嗎?A:submissions 工作表記錄所有已繳交的學生與作業組合,比對鍵為 student_id + assignment_id。這份資料通常由上一集介紹的表單提交流程(EP.4)自動寫入,不需要手動維護。只要整個自動化系列串接完整,從學生提交表單到更新繳交紀錄,再到本集的每日提醒,整條鏈路都會自動運作。 Q:這套流程可以同時追蹤多份作業嗎?A:可以。Code 節點的邏輯是「學生名單 × 啟用中作業」的全量組合比對,因此無論 assignments 表中有幾份啟用中的作業,都會在同一次執行中被一併處理。每位學生可能在同一天收到多份不同作業的提醒信(各自獨立寄出)。 Q:Cron 排程的時區設定需要注意什麼?A:n8n 的 Cron 表達式 0 9 * * * 依據的是 n8n 伺服器的系統時區。若伺服器使用 UTC,設定「早上 9 點台灣時間」應改為 0 1 * * *(UTC 1:00 = 台灣 9:00)。Code 節點內部已額外加入 UTC+8 偏移換算,確保「今日」的日期判斷以台灣時間為準,避免跨日比對錯誤。

  • article-如何打造 Google 表單作業繳交與自動寄信催繳系統? | (EP.4) n8n 自動化講師應用教學

    2026/5/4

    AI自動化 n8n 自動化講師應用
    如何打造 Google 表單作業繳交與自動寄信催繳系統? | (EP.4) n8n 自動化講師應用教學
    每次批改作業前,你是否還在手動打開試算表,一行一行比對學生名單?這種「人工核對」方式不只費時,更容易出現遺漏。這篇教學,我們將實作一套完整的 n8n 自動化系統,讓表單填交、狀態判斷到催繳通知全程零人工介入。 這套系統的核心概念是:「資料收集 → 標準化 → 期限比對 → 自動記錄」。只要學生一送出表單,整個流程就自動啟動,你只需要定期查看試算表即可。接下來,我們拆解 Flow A:Google Form 提交作業 → 寫入 submissions 的每一個節點。 整體流程架構這個自動化系統由兩個工作流組成: 工作流 功能 Flow A(本篇) Google 表單提交 → 標準化欄位 → 查詢作業設定 → 判斷準時/遲交 → 寫入 submissions Flow B(下一篇) 每日定時觸發 → 比對未交名單 → 自動寄信催繳 本篇聚焦在 Flow A,帶你從觸發器開始,逐步建立整個接收與記錄流程。 步驟一:Google Sheets Trigger — 監聽新提交許多人會直覺選用「Google Forms Trigger」,但我們這裡刻意改用 Google Sheets Trigger,原因在於 Google Form 的回覆會自動同步到「原始回覆」工作表,而 Sheets Trigger 更穩定、也更容易控制觸發時機。 設定方式: Document:選擇你的試算表 Sheet:選擇「原始回覆」分頁 Event:設為 Row Added(有新資料列加入時觸發) 每當學生提交 Google 表單,這個 Trigger 就會收到一筆新資料,並自動啟動後續流程。 步驟二:Set Submission Fields — 標準化欄位名稱Google 表單的欄位名稱是中文(例如「學號」、「時間戳記」),在跨節點傳遞時容易因編碼問題出錯。這個節點的任務,就是把中文欄位名稱對應成英文標準欄位: 表單原始欄位 標準化後欄位名稱 時間戳記 submitted_at 學號 student_id 姓名 student_name Email email 作業代號 assignment_id 作業檔案 file_url 另外也固定寫入 source: "google_form",方便未來如果有其他提交來源(如 LINE Bot、API)時,能快速識別資料來源。 步驟三:Get Assignment — 查詢作業設定資料標準化後,系統需要知道「這份作業的截止時間是什麼時候?」。這些資訊不寫死在程式碼裡,而是統一存放在 assignments 工作表中,讓你隨時調整而不需要修改 workflow。 assignments 工作表的欄位結構建議如下: 欄位 說明 作業代號 與表單選項一致,例如 homework_0102 作業名稱 作業的顯示名稱 due_at 截止時間,格式為 ISO 8601(例如 2025-01-15T23:59:00+08:00) is_active 是否啟用,填 1 表示啟用、0 表示關閉 這個節點以 assignment_id 和 is_active = 1 作為篩選條件,一次查詢就能取得對應的截止時間與作業名稱。is_active 的設計讓你在作業結束後,只需要把值從 1 改成 0,系統就不會再接受該代號的新提交。 步驟四:Compute Status — 計算準時 / 遲交狀態這是整個 Flow A 最關鍵的節點。由於 Google 表單的「時間戳記」格式是台灣慣用的 2025/1/15 上午 9:30:00,並非標準的 ISO 格式,JavaScript 原生的 new Date() 無法直接解析,因此我們需要自己撰寫解析函式。 以下是這個 Code 節點執行的三件事: 1. 解析台灣時間格式1234567891011121314function parseTaiwanDateTime(text) { const m = String(text).trim().match( /^(\d{4})\/(\d{1,2})\/(\d{1,2})\s+(上午|下午)\s+(\d{1,2}):(\d{2}):(\d{2})$/ ); if (!m) return null; let [, y, mo, d, ap, h, mi, s] = m; h = Number(h); if (ap === '下午' && h !== 12) h += 12; if (ap === '上午' && h === 12) h = 0; return { y: Number(y), mo: Number(mo), d: Number(d), h, mi: Number(mi), s: Number(s) };} 這個函式用正規表達式拆解時間字串,並正確處理「上午 12 點 = 午夜」、「下午 12 點 = 中午」這兩個 12 小時制的常見陷阱。 2. 比對截止時間,判斷狀態123456const submittedAtDate = new Date(parts.y, parts.mo - 1, parts.d, parts.h, parts.mi, parts.s);const dueAtDate = new Date(dueAtText); // due_at 為 ISO 格式,可直接解析const delayMinutes = Math.max(0, Math.floor((submittedAtDate - dueAtDate) / 60000));const status = submittedAtDate <= dueAtDate ? 'on_time' : 'late'; delayMinutes 只有在遲交時才有意義,Math.max(0, ...) 確保準時繳交的延遲值不會出現負數。 3. 提取 Google Drive 檔案 ID學生上傳的作業檔案,Google 表單只記錄一個 Drive 分享連結,實際的檔案 ID 藏在 URL 裡。我們用以下邏輯把它抽出來,方便後續流程(例如自動下載或批改)直接取用: 123456789101112function extractDriveFileId(url) { const patterns = [ /[?&]id=([a-zA-Z0-9_-]+)/, /\/d\/([a-zA-Z0-9_-]+)/, /\/file\/d\/([a-zA-Z0-9_-]+)/ ]; for (const pattern of patterns) { const match = String(url).match(pattern); if (match) return match[1]; } return '';} 這個函式相容三種常見的 Google Drive 連結格式,只要是合法的 Drive URL,都能正確提取。 步驟五:Append to submissions — 寫入資料庫完成狀態計算後,最後一步是將所有資料追加寫入 submissions 工作表,這張表就是你的「繳交記錄資料庫」。 每一筆記錄包含: 欄位 說明 assignment_id 作業代號 student_id / student_name 學生學號與姓名 email 聯絡信箱(催繳用) submitted_at 原始台灣時間字串 submitted_at_iso 轉換後的 ISO 格式時間(+08:00) due_at 作業截止時間 status on_time 或 late delay_minutes 遲交分鐘數(準時者為 0) file_url 原始分享連結 drive_file_id 抽取出的 Drive 檔案 ID 有了這張表,Flow B(自動催繳)只需要用 status = 'late' 或比對「誰的名字不在 submissions 裡」,就能精準找出需要提醒的人。 應用延伸這套流程的設計概念可以輕鬆複用到各種情境: 企業培訓:員工每月繳交學習心得,逾期自動提醒主管 合約文件催收:客戶 onboarding 必備文件,系統自動追蹤回收率 跨部門進度回報:專案每週回報表,自動標記哪些人尚未填寫 活動報名核對:比對報名名單與實際繳費記錄 只要情境符合「收件 → 對照截止時間 → 記錄狀態」的邏輯,這套架構都能直接套用。 常見問答 (FAQ)Q:為什麼用 Google Sheets Trigger,而不是直接用 Google Forms Trigger?A:Google Sheets Trigger 比 Google Forms Trigger 更穩定,觸發延遲也更低。此外,Google Form 回覆預設就會寫入連動的試算表,因此監聽 Sheets 的「新增列」事件,功能上完全等效,且更容易在試算表直接觀察與除錯資料。 Q:台灣時間的「上午/下午」格式真的不能直接用 new Date() 解析嗎?A:對,new Date("2025/1/15 上午 9:30:00") 在不同 JavaScript 環境下行為不一致,部分環境會回傳 Invalid Date。在 n8n 的 Code 節點環境中,含有「上午」、「下午」中文字符的時間字串無法被原生 Date() 正確解析,必須自行用正規表達式拆解後再重組為 Date 物件。 Q:assignments 表的 is_active 欄位有什麼用?A:這是一個「開關」設計。當一份作業的截止日已過,你只需要把 is_active 從 1 改為 0,Get Assignment 節點就找不到這筆資料,後續流程會中斷並報錯,避免過期作業繼續被記錄。這讓你不需要修改任何程式碼,純粹靠試算表資料來控制哪些作業「仍在接收中」。 Q:delay_minutes 為什麼要用 Math.max(0, …) 防止負數?A:遲交判斷式是 submittedAt - dueAt。準時繳交時,這個差值是負數,代表「提早了幾分鐘」。但 delay_minutes 欄位的語義是「延遲了多少分鐘」,對準時的同學來說應該是 0 而不是負數,Math.max(0, ...) 確保寫入資料庫的值語義清晰。 Q:drive_file_id 提取有什麼用途?A:Google Drive 的分享連結有多種格式(含 /d/、?id=、/file/d/ 等),直接儲存 URL 不方便後續程式化操作。提取 drive_file_id 後,後續節點可以直接呼叫 Google Drive API,例如:自動將檔案移到指定資料夾、設定閱讀權限、或讓 AI 節點直接讀取文件內容進行自動批改。 Q:如果學生重複提交怎麼辦?系統會判斷成最新一筆嗎?A:目前 Flow A 的設計是「每筆提交都寫入」,不會自動去重。如果你需要「同一個學生只保留最後一筆」,可以在 Append 節點之前加入一個 Google Sheets 查詢節點,先確認該 student_id + assignment_id 是否已有資料,再決定要覆寫還是新增。或者在 submissions 表的後處理(如 Flow B)中,以 student_id 分組取最新時間的那筆即可。 Q:這套系統可以只用 Google 試算表和 n8n,不需要其他付費工具嗎?A:可以,本篇所有流程都只使用 Google Sheets(免費)+ n8n(可自架 Community 版免費使用)。整套系統零額外費用,適合教育機構或預算有限的團隊直接部署。

  • article-如何建立自動化課程提醒系統?串接 Google Sheets 與自動寄信流程教學 | (EP.3) n8n 自動化講師應用教學

    2026/5/3

    AI自動化 n8n 自動化講師應用
    如何建立自動化課程提醒系統?串接 Google Sheets 與自動寄信流程教學 | (EP.3) n8n 自動化講師應用教學
    為什麼需要升級你的課程提醒系統?在上一堂課中,我們完成了基礎的課程確認信機制。今天這一集要往上一層,打造「課前提醒信」的升級版流程。 升級的核心差異在於:這次要跨兩張工作表整合資料。 正式名單:記錄學員姓名、學號、信箱、上課日期、寄信狀態 課程主表:記錄各課程的教材連結、Google Meet 連結、注意事項 光靠正式名單無法完成完整的提醒信,必須同時讀取課程主表,動態帶入課程資訊後才能寄出。這樣設計的好處是:只要更新課程主表,所有學員收到的資訊就自動跟著更新,不需要逐一手動調整。 完整工作流架構(6 個節點)1234567891011Schedule Trigger ↓Get row(s) in 課程主表 ↓Get row(s) in 正式名單 ↓篩出明天有課並合併課程主表(Code 節點) ↓Send a message(Gmail 節點) ↓Append or update row in sheet(回寫狀態) 節點詳解節點 1|Schedule Trigger — 定時觸發設定每天固定時間自動執行整條流程。測試階段可先設為「每分鐘」,正式上線後改成每天早上 08:00,讓提醒信固定在前一天早上寄出。 💡 重點:排程觸發並不代表一定會寄信。後面的 Code 節點會做篩選,若當天沒有符合條件的學員,整條流程會安靜地結束,不做任何動作。 節點 2|Get row(s) in 課程主表 — 讀取課程資訊從 Google Sheets 的「課程主表」工作表讀取所有課程資料,包含: 欄位 說明 課程名稱 用來做跨表比對的 key 上課日期 輔助比對,確認課程場次 上課方式 線上 / 實體,輔助比對 教材連結 帶入提醒信 Google meeting 連結 帶入提醒信 注意事項 帶入提醒信 這個節點先執行,讓後面的 Code 節點可以引用課程主表的資料做合併。 節點 3|Get row(s) in 正式名單 — 讀取學員名單從「正式名單」工作表讀取所有學員資料。關鍵欄位: 欄位 說明 姓名 / 學號 / 信箱 學員基本資料 課程名稱 / 上課日期 / 上課方式 用來對應課程主表 已寄確認信(Yes/No) 篩選條件之一 已寄提醒信(Yes/No) 篩選條件之一(防重複寄信的關鍵) ⚠️ 此節點開啟 Execute Once 模式,確保不論上一節點輸出幾筆課程資料,名單只會被讀取一次。 節點 4|Code 節點 — 篩選與合併(核心邏輯)這是整條流程最重要的節點。它做兩件事: 篩選:從正式名單中找出「明天上課 + 已寄確認信 + 尚未寄提醒信」的學員 合併:依課程名稱、日期、上課方式對應課程主表,帶入教材連結、會議連結與注意事項 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162const result = [];// 取得課程主表所有資料const courseRows = $("Get row(s) in 課程主表").all().map(item => item.json);// 計算明天的年月日const tomorrow = new Date();tomorrow.setDate(tomorrow.getDate() + 1);const tYear = tomorrow.getFullYear();const tMonth = tomorrow.getMonth() + 1;const tDay = tomorrow.getDate();for (const item of items) { const row = item.json; const date = row["上課日期"]; // 格式:YYYY/M/D const confirm = row["已寄確認信(Yes/No)"]; const reminder = row["已寄提醒信(Yes/No)"]; if (!date) continue; // 解析日期字串 const parts = String(date).trim().split('/'); if (parts.length !== 3) continue; const y = parseInt(parts[0], 10); const m = parseInt(parts[1], 10); const d = parseInt(parts[2], 10); // 三個篩選條件:明天上課 + 已寄確認信 + 尚未寄提醒信 if ( y === tYear && m === tMonth && d === tDay && confirm === "Yes" && reminder !== "Yes" ) { // 從課程主表找出對應課程(以課程名稱為主,日期與方式為輔) const matchedCourse = courseRows.find(course => { const sameName = String(course["課程名稱"] || "").trim() === String(row["課程名稱"] || "").trim(); const sameDate = !course["上課日期"] || String(course["上課日期"]).trim() === String(row["上課日期"] || "").trim(); const sameType = !course["上課方式"] || String(course["上課方式"]).trim() === String(row["上課方式"] || "").trim(); return sameName && sameDate && sameType; }) || {}; result.push({ json: { row_number: row["row_number"], name: row["姓名"], student_id: row["學號"], email: row["信箱"], course: row["課程名稱"], date: row["上課日期"], type: row["上課方式"], register_time: row["報名時間"], material_url: matchedCourse["教材連結"] || "", meeting_url: matchedCourse["Google meeting 連結"] || "", note: matchedCourse["注意事項"] || "" } }); }}return result; 💡 關鍵設計:reminder !== "Yes" 而非 === "No",是為了相容欄位空白的初始狀態,不需要預先手動填入 No。 節點 5|Send a message — 寄送 Gmail 提醒信使用 Gmail 節點寄送個人化課前提醒信,以動態變數帶入所有學員與課程資訊: 信件主旨: 1課前提醒|{{ $json.course }}(明天上課) 信件內文: 123456789101112131415161718Hi {{ $json.name }}({{ $json.student_id }}),您好:提醒您,您報名的課程將於明天開始。📘 課程名稱:{{ $json.course }}📅 上課日期:{{ $json.date }}📍 上課方式:{{ $json.type }}📚 教材連結:{{ $json.material_url }}💻 Google meeting 連結:{{ $json.meeting_url }}📝 注意事項:{{ $json.note }}請提前確認上課安排,謝謝! 每一位符合條件的學員都會收到專屬的個人化信件,課程資訊直接來自課程主表,不需要人工填寫。 節點 6|Append or update row — 回寫寄信狀態信件成功寄出後,立刻回寫正式名單: 欄位 寫入值 已寄確認信(Yes/No) YES 已寄提醒信(Yes/No) YES 使用學號作為 matching key,精準對應每一位學員的那一行,不會誤改到其他人的資料。 這是防止重複寄信的最後防線:下次排程觸發時,這筆學員的 已寄提醒信 已經是 YES,Code 節點的篩選條件 reminder !== "Yes" 就不會再把他納入,信件不會重複寄出。 節點式設計觀念:切入 AI Agent 前的必修課養成加 Sticky Note 的習慣工作流每個節點旁都應該加上「Sticky Note 說明卡」,清楚標明這個節點的作用、篩選條件與注意事項。工作流越長越複雜,這個習慣越重要。六個月後回來看自己的流程,不需要重新推理就能立刻讀懂。 先搞懂資料流,再用 AI AgentAI Agent 非常擅長處理彈性指令,但它並不擅長「精確控制資料欄位的讀取與寫入」。如果你還不清楚「這個節點的 input 長什麼樣、output 輸出什麼欄位」,直接交給 AI Agent 很容易產生幻覺或資料串接錯誤,而且你不知道問題在哪裡。 節點式自動化是 AI Agent 的基礎。搞懂每個節點的輸入輸出、資料結構與篩選邏輯之後,你在指揮 AI Agent 時才能給出精確指令,真正發揮自動化的價值。 常見問答 (FAQ)Q:為什麼篩選條件用 reminder !== "Yes" 而不是 === "No"?A:因為新學員剛登錄到正式名單時,「已寄提醒信」欄位通常是空白,而不是填了 No。如果用 === "No" 篩選,空白欄位的學員就會被漏掉,永遠收不到提醒信。改用 !== "Yes" 可以同時涵蓋「空白」與「No」這兩種初始狀態,新學員不需要預先手動填任何值,流程就能正確運作。 Q:日期格式不對會怎樣?流程會報錯嗎?A:Code 節點已做防護處理:若 split('/') 切割後不是三段,就直接 continue 跳過該筆資料,不會讓整條流程中斷。但學員仍然不會收到信,因此務必統一正式名單的日期格式為 YYYY/M/D(例如 2026/5/10)。可以在 Google Sheets 設定欄位格式或加入資料驗證,從來源端防止格式錯誤。 Q:課程主表找不到對應的課程時,信件會少哪些資訊?A:Code 節點在找不到對應課程時,matchedCourse 會是空物件 {},material_url、meeting_url、note 都會變成空字串 ""。信件仍然會寄出,但這三個欄位會是空白。建議在寄信後檢查一下是否有空白連結,可以在 Code 節點加一行 console.log 印出未匹配的課程名稱,方便追查是哪裡打錯字或格式不一致。 Q:已寄確認信還不是 Yes 的學員,為什麼不寄提醒信給他們?A:這是刻意設計的業務邏輯:確認信代表這位學員的報名資料已被人工審核過。如果一位學員還沒收到確認信,表示他的資料可能還在審核中,貿然寄出提醒信可能會造成混亂(例如報名失敗的學員收到提醒)。流程設計的順序是:確認信(審核通過)→ 提醒信(課前一天)。 Q:如果同一位學員報名了多門課,會正確對應到每門課的教材嗎?A:會。課程主表的比對邏輯是「課程名稱 + 上課日期 + 上課方式」三欄都相符才算匹配。只要正式名單每一行對應一筆報名記錄(一行 = 一位學員 + 一門課),就能各自找到對應的課程主表資料,不會混用。 Q:Google Sheets 回寫時,為什麼要用「學號」當 matching key,而不是行號?A:行號(row_number)在 Google Sheets 中不是穩定的識別碼。只要有人新增或刪除其他行,同一位學員的行號就會改變,回寫時就會寫到錯誤的行。「學號」是每位學員唯一且不變的識別碼,能確保 appendOrUpdate 精準更新到正確那一行,是更安全的設計。 Q:我想加入「上課前兩天」也寄一次提醒,該怎麼做?A:有兩種做法: 加一個新欄位:在正式名單新增「已寄兩天前提醒信(Yes/No)」欄位,複製一套相同的流程,把 tomorrow.setDate(tomorrow.getDate() + 1) 改為 + 2,並把篩選與回寫條件改為對應新欄位。 用排程 + 天數變數:讓 Code 節點讀取一個「提前天數」變數,由外部控制要篩選幾天後的名單,一套流程就能同時處理不同時間點的提醒。 對初學者來說,做法一比較直覺,不容易出錯。 Q:排程一直跑,但完全沒有寄出任何信,怎麼除錯?A:按以下順序逐步檢查: Code 節點輸出:手動執行流程,查看 Code 節點輸出了幾筆資料。如果是 0 筆,表示篩選條件沒有命中任何學員。 確認日期格式:正式名單的上課日期是否真的是 YYYY/M/D 格式?有沒有多餘空格或全形斜線? 確認狀態欄位:已寄確認信 是否已填 Yes(注意大小寫)?已寄提醒信 是否是空白或 No? 確認明天日期:把 Code 節點加一行 console.log(tYear, tMonth, tDay) 確認系統時區計算是否正確(n8n 的時區設定可能與你的本地時區不同)。 課程主表對應:確認課程主表中的課程名稱和正式名單完全一致,包括空格與標點符號。

  • article-如何打造全自動「課前提醒」工作流?告別手動寄信的自動化教學 | (EP.2) n8n 自動化講師應用教學

    2026/5/3

    AI自動化 n8n 自動化講師應用
    如何打造全自動「課前提醒」工作流?告別手動寄信的自動化教學 | (EP.2) n8n 自動化講師應用教學
    為什麼你需要自動化「課前提醒」流程?開課前一天,你還在逐一比對報名名單、複製貼上學員 Email、手動確認哪些人已寄、哪些人還沒寄嗎? 這樣的手動流程不只耗費時間,更容易因疏失而遺漏學員,導致出席率下降。對於同時管理多門課程的講師來說,這更是沉重的行政負擔。 透過 n8n 建立自動化工作流,系統會每天自動執行以下動作: 讀取你的 Google Sheets 報名名單 篩選出「明天上課」且「尚未收到提醒信」的學員 自動寄出客製化提醒信件 將發送狀態回寫至試算表,確保不重複發送 從此,課前提醒變成零人工介入的全自動流程。 工作流架構總覽在開始設定前,先了解整體流程的設計邏輯,有助於你在遇到問題時快速定位: 1排程觸發 → 讀取 Google Sheets → Code 節點篩選 → 發送 Email → 回寫狀態 節點 功能 說明 Schedule Trigger 定時啟動 每天指定時間自動執行 Google Sheets 讀取名單 取得所有報名學員資料 Code 條件篩選 過濾出需要提醒的對象 Email 發送信件 寄出客製化提醒信 Google Sheets 回寫狀態 標記「已寄送」避免重複 Google Sheets 欄位設定建議在開始建立工作流之前,請確認你的 Google Sheets「正式名單」工作表包含以下欄位: 欄位名稱 說明 範例值 姓名 學員姓名 王小明 學號 學員學號(作為唯一識別碼) A001 信箱 學員 Email [email protected] 課程名稱 課程名稱 AI自動化入門班 上課日期 上課日期 2026/05/10 上課方式 實體 / 線上 / 混合等 線上 已寄確認信(Yes/No) 是否已寄出報名確認信 Yes 已寄提醒信(Yes/No) 是否已寄出課前提醒信 No 重要: 上課日期 欄位請使用 YYYY/MM/DD 格式(斜線分隔),Code 節點的篩選邏輯是以此格式進行比對。 如何快速完成工作流環境部署?這套自動化流程的設計邏輯非常清晰:定時觸發 ➔ 讀取名單 ➔ 條件篩選 ➔ 發送信件 ➔ 更新狀態。以下是具體的節點設定步驟: 步驟一:設定排程觸發器 (Schedule Trigger)工作流的第一步是設定啟動時間。加入 Schedule Trigger 節點,建議設定為每天早上 8 點自動執行,確保學員在上課前一天有充裕時間收到提醒。 實際工作流中同時保留了 Manual Trigger,方便你在測試時用手動方式隨時觸發,無需等待排程時間。 開發測試建議: 先使用 Manual Trigger(點擊「Execute workflow」)手動測試整條流程 或將 Schedule Trigger 的頻率暫時改為「每分鐘」,方便即時驗證 確認整體流程無誤後,再改回每天一次的排程 步驟二:讀取 Google Sheets 名單加入 Google Sheets 節點,選擇「Get Many Rows」操作,一次讀取「正式名單」工作表的所有資料。 設定要點: Spreadsheet:選擇你的報名表試算表 Sheet:選擇「正式名單」工作表 資料全部抓回後,交給後面的 Code 節點篩選——這樣最穩,不容易因試算表結構變動而出錯 步驟三:篩選需要提醒的學員 (Code 節點)這是整個工作流的核心邏輯。加入 Code 節點(節點名稱:「篩出明天有課」),貼入以下篩選程式碼: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748const result = [];const tomorrow = new Date();tomorrow.setDate(tomorrow.getDate() + 1);const tYear = tomorrow.getFullYear();const tMonth = tomorrow.getMonth() + 1;const tDay = tomorrow.getDate();for (const item of items) { const row = item.json; const date = row["上課日期"]; const confirm = row["已寄確認信(Yes/No)"]; const reminder = row["已寄提醒信(Yes/No)"]; if (!date) continue; const parts = String(date).trim().split('/'); if (parts.length !== 3) continue; const y = parseInt(parts[0], 10); const m = parseInt(parts[1], 10); const d = parseInt(parts[2], 10); if ( y === tYear && m === tMonth && d === tDay && confirm === "Yes" && reminder !== "Yes" ) { result.push({ json: { row_number: row["row_number"], name: row["姓名"], student_id: row["學號"], email: row["信箱"], course: row["課程名稱"], date: row["上課日期"], type: row["上課方式"], register_time: row["報名時間"] } }); }}return result; 程式碼說明: 明天日期拆解為年、月、日三個數字,再與試算表的 YYYY/MM/DD 格式逐項比對,避免字串比較因格式差異失敗 三重條件過濾:日期符合 且 已寄確認信 = Yes 且 尚未寄提醒信 「已寄確認信」的前提很重要——代表學員已完成報名流程,才需要發提醒 Code 節點同時將中文欄位名稱重新命名為英文(name、email、student_id…),讓後續的 Gmail 節點可以用更簡潔的變數名稱取值 步驟四:自動發送客製化 Email 提醒信確認篩選名單後,加入 Gmail 節點(使用 Gmail OAuth2 授權)。因為前面的 Code 節點已將欄位重新命名為英文,這裡可以直接使用簡潔的變數名稱: 收件人:{{ $json.email }} 主旨:課前提醒|{{ $json.course }} 信件內文範例: 123456789Hi {{ $json.name }}({{ $json.student_id }}),您好:提醒您,您報名的課程即將開始。📘 課程名稱:{{ $json.course }}📅 上課日期:{{ $json.date }}📍 上課方式:{{ $json.type }}請提前確認上課安排,謝謝! 注意:Gmail 節點的「Append Attribution」建議關閉(appendAttribution: false),避免信件底部出現 n8n 的廣告署名,影響專業形象。 步驟五:回寫狀態,杜絕重複發送這是整個工作流中最不能遺漏的一環。信件成功寄出後,必須回到 Google Sheets 將學員的提醒信狀態更新為 Yes。 加入第二個 Google Sheets 節點,操作選擇「Append or Update Row」: Matching Column(比對欄位):學號——以學號作為唯一識別,確保更新到正確的列 更新欄位:已寄提醒信(Yes/No) 設定為 Yes 來源資料從 Code 節點取得:{{ $('篩出明天有課').item.json.student_id }} 使用「學號」而非「列號」做比對有一個重要優點:即使試算表的排序或列數改變,更新也不會寫入錯誤的列。 若沒有這個步驟,下次排程執行時,系統會對同一批學員再次寄信,造成學員困擾與信任損失。 下一步:讓工作流更進階完成這套課前提醒後,你已省下每次開課前大量的手動作業時間。在下一堂進階課程中,我們將教你如何: 在信件中動態帶入專屬課程連結與 Google Meet 視訊網址 加入課前注意事項附件,打造更專業的學員體驗 建立多課程並行管理的工作流架構,適用於同時開設多門課的講師 常見問答 (FAQ)Q:Google Sheets 欄位名稱和教學不一樣,程式碼需要完整改寫嗎?A:不需要。只需在 Code 節點中修改對應的欄位名稱即可。例如,若你的日期欄位叫做「課程日期」而非「上課日期」,只需將程式碼中的 row["上課日期"] 替換為 row["課程日期"],其餘篩選邏輯完全不變。Google Sheets 回寫節點的 Matching Column 也需要同步修改。建議先在試算表統一欄位命名,後續維護會更輕鬆。 Q:如果想改成「開課前三天」寄送提醒,如何調整?A:在 Code 節點中,將日期計算的 +1 改為 +3 即可: 12345// 修改前(明天)tomorrow.setDate(tomorrow.getDate() + 1);// 修改後(三天後)tomorrow.setDate(tomorrow.getDate() + 3); 其餘的 Gmail 節點與回寫邏輯完全不需要變動。若你想同時在「三天前」和「一天前」各發一次提醒,建議分別建立兩條獨立的工作流,各自有各自的回寫欄位(如「已寄三日提醒信」和「已寄前日提醒信」),這樣狀態管理最清晰,不容易互相干擾。 Q:測試時不小心寄出太多信怎麼辦?A:在開發與測試階段,請採取以下保護措施: 修改收件人:在 Email 節點暫時將收件人從 {{ $json.Email }} 改為你自己的測試信箱 停用 Email 節點:在工作流中右鍵點擊 Email 節點,選擇「Disable」,僅觀察資料流輸出的 JSON 內容 限制資料筆數:在 Google Sheets 節點設定「Limit」,一次只讀取 1~2 筆資料進行測試 確認篩選條件與回寫狀態(Yes/No)都正確後,再關閉所有限制、改回真實收件人。 Q:為什麼系統會重複寄信給同一位學員?A:這是最常見的錯誤,通常由以下原因造成: 步驟五的回寫節點未正確執行:請確認工作流最後有成功更新「已寄提醒信(Yes/No)」欄位。執行後手動打開試算表,確認欄位值是否已改為 Yes。 欄位名稱不完全一致:Code 節點中的 row["已寄提醒信(Yes/No)"] 必須與 Google Sheets 的欄位標題逐字相同,包含括號與標點符號,差一個字就會讀到 undefined,導致條件永遠成立。 Matching Column 設定錯誤:回寫節點若 Matching Column 設定不正確,更新可能寫入到錯誤的列,讓原始列的狀態維持 No。 除錯建議:在 Code 節點篩選結果中,暫時加入 console.log(JSON.stringify(row)) 印出每筆資料的完整欄位名稱,就能快速確認試算表傳回的欄位名稱是否與程式碼一致。 Q:我的課程同時有多個梯次,同一門課有不同上課日期,工作流支援嗎?A:完全支援,工作流的設計本身就是逐筆比對每位學員的 ClassDate,因此同一門課的不同梯次只要在 Google Sheets 中各自有一列記錄,系統就能精準對應每位學員的實際上課日期,不會混淆。若你的報名表裡有多門課程,同樣適用,篩選邏輯只關心「日期」和「狀態」,與課程名稱無關。 Q:工作流使用的是 Gmail 節點,還是一般的 Email 節點?有什麼差別?A:本工作流使用的是 Gmail 節點(搭配 Gmail OAuth2 授權),不是通用的 Email (SMTP) 節點。兩者的差異如下: 節點 授權方式 適合情境 Gmail Google OAuth 授權 使用 Gmail 帳號發信,設定簡單、顯示名稱友善 Email (SMTP) 填入 SMTP 伺服器設定 使用自訂網域信箱(如 [email protected]) 若你已有 Google Workspace 帳號,Gmail 節點是最快速的選擇——完成 OAuth 授權後即可使用,無需額外的 SMTP 設定。若你希望以機構網域信箱發信,則改用 SMTP 節點,品牌形象更專業,但需要向你的郵件服務商取得 SMTP 憑證。 Q:如果某位學員的 Email 信箱填寫錯誤,工作流會報錯嗎?A:當 Email 節點遇到無效信箱(如格式錯誤或不存在的地址),工作流預設會中斷並報錯,導致後續學員的信件也無法寄出,狀態也不會被回寫。 建議解法:在 Email 節點的「Settings」中開啟「Continue On Fail」選項,讓工作流在遇到單一錯誤時跳過該筆資料,繼續處理下一位學員。同時,可以在後方加入一個通知節點(如 Line Notify 或 Slack),在寄信失敗時主動告警,讓你能即時追蹤並手動補寄。 Q:n8n 自架版(Self-hosted)和 n8n Cloud 的設定方式有差異嗎?A:工作流的節點邏輯和設定方式完全相同,差異主要在環境維護層面: n8n Cloud:免維護、開箱即用,適合個人講師或小型工作室,但有使用量限制與月費 Self-hosted:需自行部署伺服器(如 Railway、Render 或 VPS),免費且可無限使用,但需負責更新與備份 若你是第一次建立自動化工作流,建議先從 n8n Cloud 的免費方案開始體驗,熟悉流程後再考慮自架。

  • article-打造 Google 表單與 Gmail 自動回信報名系統 | (EP.1) n8n 自動化講師應用教學

    2026/5/2

    AI自動化 n8n 自動化講師應用
    打造 Google 表單與 Gmail 自動回信報名系統 | (EP.1) n8n 自動化講師應用教學
    為什麼你需要一套自動化報名系統?今天我們要實作一個很多講師與活動主辦人夢寐以求的工具:用 n8n 打造 Google 表單 × Gmail 全自動報名回信系統。 你是否曾有這樣的經驗:活動開放報名後,一個下午都在反覆開啟試算表,手動一筆一筆貼上確認信收件人,再逐一寄出?這種流程不只費時,還容易漏寄或寄錯。 透過本文介紹的 n8n 自動化流程,只要學員送出 Google 表單,系統就會自動: 彙整報名資料到試算表 篩選並建立正式名單 發送個人化確認信 更新寄件完成狀態 從此,報名通知這件事,你完全不需要再碰。 自動化工作流的五大關鍵步驟這套系統的核心邏輯分為以下五個步驟,環環相扣、自成閉環: 學員提交表單: 學員填寫包含姓名、學號、Email、課程名稱等資訊的 Google 報名表單並送出。 原始資料彙整: 表單回應自動同步至 Google 試算表的「原始資料」工作表,作為所有後續處理的來源。 正式名單拋轉: n8n 定期掃描原始資料,將尚未處理的新報名者篩選出來,整理後寫入「正式名單」工作表。 自動觸發寄信: 根據正式名單中的 Email 與報名資訊,系統透過 Gmail 自動發送個人化確認信,信件內容可包含上課時間、地點或 Google Meet 連結。 更新寄件狀態: 信件成功送出後,系統自動將該筆資料的寄信欄位標記為「Yes」,完成整個自動化閉環,方便隨時查閱進度。 關鍵設計概念: 以「寄信狀態是否為 Yes」作為判斷依據,確保每位報名者只收到一封確認信,不重複、不漏寄。 拆解 n8n 節點:五個節點完成全流程在 n8n 畫布中,我們用五個節點串起整套流程。以下逐一說明各節點的用途與設定重點: 1. Schedule Trigger(定時排程觸發)設定每分鐘(或自訂頻率)自動觸發流程。n8n 會定期「醒來」掃描試算表,確認是否有新報名者需要處理。 2. Google Sheets:讀取原始資料撈取「原始資料」工作表中所有尚未同步至正式名單的新資料列,作為後續節點的輸入來源。 3. Google Sheets:寫入正式名單將清洗過的報名資料(姓名、Email、課程代碼等)寫入正式名單工作表,同時預設「確認信」欄位為空白,作為待處理的標記。 4. Gmail Node(發送確認信)從正式名單中讀取 Email,套入預先設計好的信件範本,自動發送報名成功通知。信件內容可使用 n8n 的表達式(Expression)動態帶入學員姓名、課程名稱等個人化資訊。 5. Google Sheets:更新寄件狀態這是整套流程最關鍵的一步。Gmail 節點成功執行後,立即回寫試算表,將該筆資料的「確認信」欄位更新為「Yes」。若寄信失敗,此步驟不會執行,狀態維持空白,讓管理員一眼識別異常並手動補寄。 延伸應用:上課前提醒信這套邏輯同樣可以套用在「活動前一天的提醒信」:新增一個獨立的 n8n 排程,掃描正式名單中「提醒信」欄位為空白的學員,批次發信並標記「Yes」,一套模板即可重複使用。 常見問答 (FAQ)Q:我沒有程式背景,能自己做出這套流程嗎?A:完全可以!n8n 採用視覺化的節點拖拉介面,不需要撰寫任何程式碼。本文及對應的 YouTube 教學影片都有完整的逐步示範,只要跟著操作就能建立起整套流程。建議先用 n8n Cloud 試用版(免費)快速上手,再視需求考慮自架。 Q:n8n 的觸發頻率可以自訂嗎?每分鐘跑一次會不會太頻繁?A:可以完全自訂。「每分鐘」是教學中用於快速測試的設定。正式部署時,建議根據活動規模調整:一般課程每 5~15 分鐘跑一次即可。n8n 的 Schedule Trigger 支援 cron 語法,可設定任意時間間隔或指定時段(例如僅在工作時間執行)。 Q:如果報名者填錯 Email,系統會怎麼處理?A:若 Email 格式錯誤,Gmail Node 會拋出錯誤並中斷後續流程,「更新寄件狀態為 Yes」的步驟便不會執行。管理員只需過濾試算表中「確認信」欄位為空白的列,即可快速找出所有未成功寄達的報名者,進行人工確認與補寄。這個設計確保了問題的可追蹤性。 Q:這套流程使用的 Google 服務需要付費嗎?A:不需要。Google Forms、Google Sheets 與 Gmail 均為免費方案即可使用,沒有特殊 API 費用。n8n 本身提供免費的雲端試用方案(n8n Cloud),若需大量執行或長期使用,可選擇付費方案或自架免費的開源版本。 Q:確認信的內容可以個人化嗎?能自動帶入學員姓名嗎?A:可以。n8n 的 Gmail Node 支援 Expression(表達式)功能,可以直接引用前置節點傳入的欄位變數,例如 {{ $json.name }} 帶入姓名、{{ $json.course }} 帶入課程名稱。不需要任何程式基礎,直接在信件編輯框中點選「插入變數」即可完成個人化設定。 Q:這套系統可以同時管理多個課程或活動嗎?A:可以。有兩種常見做法:(1)分開工作表: 在同一份 Google 試算表中,為每個課程新增獨立的工作表,搭配對應的 n8n 流程或分支判斷節點;(2)統一管理: 在表單中加入「課程名稱」欄位,以此欄位進行篩選,在同一套 n8n 流程中以條件分支(If Node)分流處理不同課程的信件範本。 Q:這套流程可以套用到企業內部的報名系統嗎?A:完全適用。企業內部的講座、教育訓練、會議邀約等場景,只需替換 Google 表單欄位與 Gmail 信件範本即可直接套用。更進一步,企業用戶可搭配 Google Workspace 的共享試算表,讓多位管理員同時查看報名狀態,達到跨部門協作的效果。