前言

每個工程師都有一些「每天都在做但其實可以自動化」的事情。收到客戶信件要轉到 Slack、每週要整理報表、PR 合併後要更新文件……這些事情一件一件做不累,但加起來每週可能吃掉你好幾個小時。

n8n 是一個開源的工作流自動化平台,有點像 Zapier 但可以自己架、不用付訂閱費。更重要的是,它最近加入了 AI 節點,讓你可以在自動化流程中串接 LLM,做一些以前「只有人能做」的判斷和處理。

這篇文章分享我用 n8n 建立的幾個實用自動化案例,從安裝到實際的 workflow 設計。

安裝部署

Docker Compose 部署(推薦)

# docker-compose.yml
services:
  n8n:
    image: n8nio/n8n
    container_name: n8n
    restart: always
    ports:
      - "5678:5678"
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=admin
      - N8N_BASIC_AUTH_PASSWORD=your-secure-password
      - N8N_HOST=localhost
      - N8N_PORT=5678
      - N8N_PROTOCOL=http
      - GENERIC_TIMEZONE=Asia/Taipei
      - TZ=Asia/Taipei
      # 啟用社群節點(可選)
      - N8N_COMMUNITY_PACKAGES_ALLOW_TOOL_USAGE=true
    volumes:
      - n8n_data:/home/node/.n8n
    # 如果要跟本地的 Ollama 通訊
    extra_hosts:
      - "host.docker.internal:host-gateway"

# 搭配 PostgreSQL 做持久化(正式環境建議) postgres: image: postgres:16-alpine container_name: n8n-db restart: always environment: - POSTGRES_DB=n8n - POSTGRES_USER=n8n - POSTGRES_PASSWORD=n8n-password volumes: - postgres_data:/var/lib/postgresql/data

volumes: n8n_data: postgres_data:

# 啟動
docker compose up -d

# 打開瀏覽器 open http://localhost:5678

# 查看日誌 docker compose logs -f n8n

搭配 Ollama 的完整部署

# docker-compose.yml(n8n + Ollama 一起部署)
services:
  n8n:
    image: n8nio/n8n
    container_name: n8n
    restart: always
    ports:
      - "5678:5678"
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=admin
      - N8N_BASIC_AUTH_PASSWORD=your-secure-password
      - GENERIC_TIMEZONE=Asia/Taipei
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      - ollama

ollama: image: ollama/ollama container_name: ollama restart: always ports: - "11434:11434" volumes: - ollama_data:/root/.ollama

volumes: n8n_data: ollama_data:

Workflow 設計基礎

n8n 核心概念

Trigger (觸發) → Node (處理) → Node (處理) → ... → Output (輸出)

常用 Trigger:

  • Webhook: 接收 HTTP 請求
  • Schedule: 定時執行(cron)
  • Email Trigger: 收到郵件時
  • Telegram Trigger: 收到訊息時

常用 Node:

  • HTTP Request: 呼叫外部 API
  • Code: 執行 JavaScript/Python
  • IF: 條件分支
  • Switch: 多路分支
  • AI Agent: LLM 處理
  • Set: 設定/轉換資料

第一個 Workflow:定時健康檢查

{
  "name": "Service Health Check",
  "nodes": [
    {
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [{ "field": "minutes", "minutesInterval": 5 }]
        }
      }
    },
    {
      "name": "Check Services",
      "type": "n8n-nodes-base.httpRequest",
      "parameters": {
        "url": "http://your-api.com/health",
        "method": "GET",
        "options": {
          "timeout": 5000
        }
      }
    },
    {
      "name": "Check Status",
      "type": "n8n-nodes-base.if",
      "parameters": {
        "conditions": {
          "number": [{
            "value1": "={{ $json.statusCode }}",
            "operation": "notEqual",
            "value2": 200
          }]
        }
      }
    },
    {
      "name": "Send Alert",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#alerts",
        "text": "Service is DOWN! Status: {{ $json.statusCode }}"
      }
    }
  ]
}

實戰案例

案例 1:AI 郵件分類與回覆

這是我最常用的 workflow。收到客戶郵件後,AI 自動分類並草擬回覆。

// n8n Code Node:郵件分類邏輯
// 這個 Code 節點在收到郵件後執行

const email = $input.first().json;

// 呼叫 Ollama 做郵件分類 const response = await fetch('http://ollama:11434/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'llama3.1', prompt: 分類以下郵件。只回覆一個分類標籤。

可能的分類:

  • bug_report(Bug 回報)
  • feature_request(功能需求)
  • general_inquiry(一般詢問)
  • urgent(緊急問題)

郵件主旨:${email.subject} 郵件內容:${email.text}

分類:, stream: false, options: { temperature: 0.1 } }) });

const result = await response.json(); const category = result.response.trim().toLowerCase();

return [{ json: { ...email, ai_category: category, processed_at: new Date().toISOString() } }];

// n8n Code Node:AI 草擬回覆

const email = $input.first().json;

const response = await fetch('http://ollama:11434/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'llama3.1', prompt: 你是一位客服工程師。請根據以下客戶郵件草擬一份專業友善的回覆。 用繁體中文回覆。

分類:${email.ai_category} 客戶主旨:${email.subject} 客戶內容:${email.text}

回覆草稿:, stream: false, options: { temperature: 0.5, num_predict: 500 } }) });

const result = await response.json();

return [{ json: { original_email: email, draft_reply: result.response, needs_review: email.ai_category === 'urgent' } }];

案例 2:PR 合併後自動更新 Changelog

// Webhook Trigger 接收 GitHub webhook (pull_request event)

const payload = $input.first().json;

// 只處理 merged 的 PR if (payload.action !== 'closed' || !payload.pull_request.merged) { return []; }

const pr = payload.pull_request;

// 用 AI 生成 changelog 條目 const response = await fetch('http://ollama:11434/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'llama3.1', prompt: 根據以下 Pull Request 資訊,生成一條簡潔的 changelog 條目。 格式:- [類型] 描述 (#PR號) 類型可以是:feat, fix, docs, refactor, test, chore

PR 標題:${pr.title} PR 描述:${pr.body} PR 號碼:#${pr.number} 變更的檔案:${pr.changed_files} 個

Changelog 條目:, stream: false, options: { temperature: 0.2 } }) });

const changelogEntry = (await response.json()).response.trim();

return [{ json: { pr_number: pr.number, pr_title: pr.title, changelog_entry: changelogEntry, merged_at: pr.merged_at, author: pr.user.login } }];

案例 3:每日技術新聞摘要

// Schedule Trigger: 每天早上 8:00

// Step 1: 抓取 Hacker News 前 10 則 const hnResponse = await fetch( 'https://hacker-news.firebaseio.com/v0/topstories.json' ); const topIds = (await hnResponse.json()).slice(0, 10);

const stories = []; for (const id of topIds) { const storyRes = await fetch( https://hacker-news.firebaseio.com/v0/item/${id}.json ); const story = await storyRes.json(); stories.push({ title: story.title, url: story.url || https://news.ycombinator.com/item?id=${id}, score: story.score, comments: story.descendants || 0 }); }

// Step 2: 用 AI 生成中文摘要 const storyList = stories .map((s, i) => ${i + 1}. ${s.title} (${s.score} points)) .join('\n');

const summaryRes = await fetch('http://ollama:11434/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'llama3.1', prompt: 以下是今天 Hacker News 的熱門文章。 請用繁體中文為每一則寫一句話的摘要,並標注跟後端工程相關的文章。

${storyList}

摘要:, stream: false, options: { temperature: 0.3, num_predict: 1000 } }) });

const summary = (await summaryRes.json()).response;

// Step 3: 組裝 Slack 訊息 const slackMessage = <em>:newspaper: 今日技術新聞摘要</em>\n\n${summary}\n\n + stories.map(s => • &lt;${s.url}|${s.title}&gt; (${s.score} pts)).join('\n');

return [{ json: { message: slackMessage, story_count: stories.length, generated_at: new Date().toISOString() } }];

案例 4:文件變更通知

// Webhook: 接收 Git push event

const payload = $input.first().json; const commits = payload.commits || [];

// 找出改了文件的 commit const docChanges = commits.filter(commit => { const allFiles = [ ...(commit.added || []), ...(commit.modified || []), ]; return allFiles.some(f => f.endsWith('.md') || f.includes('docs/') || f.includes('README') ); });

if (docChanges.length === 0) { return []; // 沒有文件變更,不通知 }

// 整理變更清單 const changes = docChanges.map(commit => ({ message: commit.message, author: commit.author.name, files: [...(commit.added || []), ...(commit.modified || [])] .filter(f => f.endsWith('.md') || f.includes('docs/')), timestamp: commit.timestamp }));

return [{ json: { changes, repo: payload.repository.full_name, branch: payload.ref.replace('refs/heads/', ''), notification_text: 文件更新:${changes.length} 個 commit 修改了文件 } }];

進階技巧

錯誤處理

// 在 Code 節點中做完善的錯誤處理

try { const response = await fetch('http://ollama:11434/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'llama3.1', prompt: $input.first().json.prompt, stream: false, }), signal: AbortSignal.timeout(30000), // 30 秒超時 });

if (!response.ok) { throw new Error(Ollama API 回傳 ${response.status}); }

const result = await response.json(); return [{ json: { success: true, response: result.response } }];

} catch (error) { // 記錄錯誤但不中斷 workflow console.error('AI 處理失敗:', error.message); return [{ json: { success: false, error: error.message, fallback: '(AI 處理失敗,需要人工處理)' } }]; }

環境變數管理

# .env 檔案
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=change-me
OLLAMA_BASE_URL=http://ollama:11434
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/xxx/yyy/zzz
GITHUB_TOKEN=ghp_xxxxxxxxxxxxx
# docker-compose.yml 中引用
services:
  n8n:
    env_file:
      - .env
    environment:
      - N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER}
      - N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD}

效能與安全注意事項

  1. API 限流:呼叫外部 API 時要注意 rate limit,在 n8n 中可以用 Wait 節點做間隔
  1. 資料保留:n8n 預設會保留所有執行紀錄,時間久了硬碟會爆。設定 EXECUTIONS_DATA_MAX_AGE=168(小時)自動清理
  1. Webhook 安全:對外的 webhook 要加驗證。GitHub webhook 可以用 secret token 驗證簽章
  1. LLM 回應不穩定:AI 的輸出不是 100% 可預測的。重要的自動化流程,AI 的輸出最好只做「建議」,不要直接當「決策」

小結

n8n + AI 是一個很強大的組合。n8n 負責「什麼時候做」和「怎麼串」,AI 負責「理解」和「判斷」。以前很多需要人工介入的自動化流程,現在都可以用 AI 節點來填補那個「需要思考」的缺口。

我的建議是:

  1. 先從簡單的非 AI 自動化開始,熟悉 n8n 的 workflow 概念
  2. 加入 AI 節點處理「需要判斷」的步驟,比如分類、摘要、草擬回覆
  3. 永遠保留人工 review 的環節,至少在初期不要讓 AI 全自動執行關鍵操作
  4. 監控和記錄,建立 dashboard 追蹤自動化的執行狀況

延伸閱讀:

  • n8n 官方文件:https://docs.n8n.io
  • n8n 社群 workflow 範本庫
  • Zapier vs n8n 比較
  • Make (Integromat) — 另一個自動化平台