AutomationFlowsAI & RAG › Send Daily AI Vocabulary Lessons with Gpt-4o-mini, Google Sheets and Gmail

Send Daily AI Vocabulary Lessons with Gpt-4o-mini, Google Sheets and Gmail

ByisaWOW @isawow on n8n.io

Fill in a form with your exam date, subjects, daily available hours, and study start time, and this workflow generates a complete personalised day-by-day study schedule using GPT-4o-mini, adds every session to Google Calendar automatically, logs the full plan to Google Sheets,…

Cron / scheduled trigger★★★★☆ complexityAI-powered17 nodesGoogle SheetsAgentOpenAI ChatGmail
AI & RAG Trigger: Cron / scheduled Nodes: 17 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #16058 — we link there as the canonical source.

This workflow follows the Agent → Gmail recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "id": "VvjBkO2INDcieJDV",
  "name": "Vocabulary Builder \u2014 Daily Word Practice with GPT-4o-mini + Google Sheets + Gmail",
  "tags": [],
  "nodes": [
    {
      "id": "c57d285f-9c05-4847-9a66-d12e25709a5b",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -960,
        -800
      ],
      "parameters": {
        "color": 4,
        "width": 540,
        "height": 1412,
        "content": "## Vocabulary Builder \u2014 Daily Word Practice with GPT-4o-mini + Google Sheets + Gmail\n\nFor language learners, students preparing for competitive exams (GRE, IELTS, TOEFL, CAT), and professionals building vocabulary in English, Spanish, French, German, or Hindi. A schedule trigger fires daily. User preferences are read from the User Config tab in Google Sheets \u2014 each row is one user with their name, email, target language, difficulty level, daily word count, and topic focus. SplitInBatches processes each user individually. Word History is read from the second Sheets tab to track all previously sent words. A Code node filters history by language, extracts the last 60 words, and prepares the AI prompt. GPT-4o-mini generates fresh words never sent before \u2014 with definition, pronunciation, part of speech, example sentence, and a mnemonic memory tip for each word. A Code node parses the AI output and builds a rich HTML email with word cards and a 3-question multiple-choice quiz. A second Code node spreads word rows for Sheets. Google Sheets appends each new word to the history. Gmail sends the personalized daily vocabulary email to each user.\n\n## How it works\n- **1. Schedule \u2014 Every Day at 7 AM** triggers the pipeline daily\n- **2. Google Sheets \u2014 Read User Config** reads all rows from the User Config tab \u2014 each row is one subscriber\n- **3. SplitInBatches \u2014 One User at a Time** loops each user row individually\n- **4. Google Sheets \u2014 Read Word History** reads all rows from the Word History tab\n- **5. Code \u2014 Prepare AI Prompt** filters history by the current user language, extracts the last 60 words, and builds the structured prompt\n- **6. AI Agent \u2014 Generate Vocabulary** uses GPT-4o-mini to generate fresh words with definitions, pronunciations, example sentences, memory tips, and a 3-question MCQ quiz\n- **OpenAI \u2014 GPT-4o-mini Model** language model attached to the AI Agent\n- **7. Code \u2014 Format Email and Words** parses AI JSON, builds the HTML email with word cards and quiz, and prepares the words_to_save array\n- **8. Code \u2014 Spread Word Rows** splits the words array into individual items for Sheets\n- **9. Google Sheets \u2014 Append Word History** appends one row per new word to the Word History tab\n- **10. Gmail \u2014 Send Vocabulary Email** sends the personalized daily email to the user\n\n## Google Sheets setup\nCreate one Google Sheet with two tabs:\n\n**Tab 1 \u2014 User Config** (columns): Name | Email | Language | Difficulty | Daily Words Count | Topic Focus | Status\n- Language options: English, Spanish, French, German, Hindi\n- Difficulty options: Beginner, Elementary, Intermediate, Advanced, Expert\n- Topic Focus options: General, Business, Technology, Medical, Academic, IELTS-TOEFL, GRE-GMAT\n- Status: Active (only Active rows are processed)\n- Add one row per subscriber\n\n**Tab 2 \u2014 Word History** (columns): Date | User Email | Language | Word | Definition | Example Sentence | Difficulty\n- Leave empty initially \u2014 the workflow fills it automatically\n\n## Set up steps\n1. Create the Google Sheet with both tabs as described above\n2. In **2. Google Sheets \u2014 Read User Config** \u2014 connect Google Sheets OAuth2 credential and replace `YOUR_GOOGLE_SHEET_ID`\n3. In **4. Google Sheets \u2014 Read Word History** \u2014 connect the same credential and replace `YOUR_GOOGLE_SHEET_ID`\n4. In **OpenAI \u2014 GPT-4o-mini Model** \u2014 connect your OpenAI API credential\n5. In **9. Google Sheets \u2014 Append Word History** \u2014 connect the same credential and replace `YOUR_GOOGLE_SHEET_ID`\n6. In **10. Gmail \u2014 Send Vocabulary Email** \u2014 connect Gmail OAuth2 credential"
      },
      "typeVersion": 1
    },
    {
      "id": "346ca96e-d57b-467c-9d68-1503c99add13",
      "name": "Section \u2014 Daily Schedule and User Config Read",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -336,
        -448
      ],
      "parameters": {
        "color": 5,
        "width": 436,
        "height": 420,
        "content": "## Daily Schedule and User Config Read\nSchedule fires every day at 7 AM. Sheets reads all rows from the User Config tab \u2014 each row is one subscriber with language, difficulty, topic focus, and email."
      },
      "typeVersion": 1
    },
    {
      "id": "5072f164-691f-4c3b-afcf-96f3fb9a9418",
      "name": "Section \u2014 Per-User Split and Word History Read",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        128,
        -384
      ],
      "parameters": {
        "color": 6,
        "width": 468,
        "height": 308,
        "content": "## Per-User Split and Word History Read\nSplitInBatches loops each user row. Sheets reads all Word History rows. Code in next node filters by this user language to find previous words."
      },
      "typeVersion": 1
    },
    {
      "id": "2f686151-17a9-4d78-8dc5-27fe487f0a4a",
      "name": "Section \u2014 Prompt Preparation and GPT-4o-mini Vocabulary Generation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        -496
      ],
      "parameters": {
        "color": 6,
        "width": 532,
        "height": 644,
        "content": "## Prompt Preparation and GPT-4o-mini Vocabulary Generation\nCode filters history by language and extracts last 60 words to prevent repeats. GPT-4o-mini generates fresh words with definitions, pronunciations, example sentences, memory tips, and a 3-question MCQ quiz."
      },
      "typeVersion": 1
    },
    {
      "id": "6d68328f-ed73-466d-881a-4cd44faabf96",
      "name": "Section \u2014 Email Format, Word Row Spread, and Sheets Append",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1200,
        -384
      ],
      "parameters": {
        "color": 6,
        "width": 676,
        "height": 308,
        "content": "## Email Format, Word Row Spread, and Sheets Append\nCode parses AI output and builds HTML email with word cards and quiz. Second Code spreads word rows for Sheets. Sheets appends one row per new word to Word History tab."
      },
      "typeVersion": 1
    },
    {
      "id": "20d4f13b-6ef9-4ce8-ae86-c150b9a011d9",
      "name": "Section \u2014 Gmail Send",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1904,
        -496
      ],
      "parameters": {
        "color": 4,
        "width": 276,
        "height": 500,
        "content": "## Gmail Send\nSends personalized daily vocabulary email to each user. Uses node 7 first() for email content and user address."
      },
      "typeVersion": 1
    },
    {
      "id": "014f1bf9-8c6c-4d3f-82e3-5530ebdedd83",
      "name": "1. Schedule \u2014 Every Day at 7 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -272,
        -272
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "630ab4e1-5e04-4aa4-9b74-ef6b4d91d924",
      "name": "2. Google Sheets \u2014 Read User Config",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -32,
        -272
      ],
      "parameters": {
        "operation": "getAll",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "8eda0694-4304-4907-954c-83a7828336b1",
      "name": "3. SplitInBatches \u2014 One User at a Time",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        208,
        -272
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "e3bcf30a-b2e6-4108-83bf-0961a016c55a",
      "name": "4. Google Sheets \u2014 Read Word History",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        448,
        -272
      ],
      "parameters": {
        "operation": "getAll",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "f8d44380-fa64-4d71-adcb-a47e9e772ae0",
      "name": "5. Code \u2014 Prepare AI Prompt",
      "type": "n8n-nodes-base.code",
      "position": [
        688,
        -272
      ],
      "parameters": {
        "jsCode": "// Get current user config from SplitInBatches\nconst user = $('3. SplitInBatches \u2014 One User at a Time').item.json;\n\n// Skip inactive users\nif (!user['Status'] || user['Status'].trim().toLowerCase() !== 'active') {\n  return [];\n}\n\nconst userName      = user['Name']             || 'Learner';\nconst userEmail     = user['Email']            || '';\nconst language      = user['Language']         || 'English';\nconst difficulty    = user['Difficulty']       || 'Intermediate';\nconst dailyCount    = parseInt(user['Daily Words Count']) || 5;\nconst topicFocus    = user['Topic Focus']      || 'General';\n\nif (!userEmail) return [];\n\n// Filter word history for this user + language\nconst allHistory = $input.all();\nconst langHistory = allHistory\n  .filter(item => {\n    const row = item.json;\n    return row['Language'] && row['Language'].toLowerCase() === language.toLowerCase()\n        && row['User Email'] && row['User Email'].toLowerCase() === userEmail.toLowerCase();\n  })\n  .map(item => item.json['Word'])\n  .filter(Boolean);\n\n// Take last 60 unique words to pass to AI\nconst recentWords = [...new Set(langHistory)].slice(-60);\nconst prevWordsStr = recentWords.length > 0\n  ? recentWords.join(', ')\n  : 'none yet';\n\n// Total words learned (for email header)\nconst totalLearned = langHistory.length;\n\nreturn [{ json: {\n  user_name: userName,\n  user_email: userEmail,\n  language,\n  difficulty,\n  daily_count: dailyCount,\n  topic_focus: topicFocus,\n  previous_words: prevWordsStr,\n  total_learned: totalLearned\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "a3031399-bcd1-45f6-9893-85d98973acae",
      "name": "6. AI Agent \u2014 Generate Vocabulary",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        928,
        -272
      ],
      "parameters": {
        "text": "=Language: {{ $json.language }}\nDifficulty Level: {{ $json.difficulty }}\nWords to Generate: {{ $json.daily_count }}\nTopic Focus: {{ $json.topic_focus }}\nLearner Name: {{ $json.user_name }}\nTotal Words Learned So Far: {{ $json.total_learned }}\nWords Already Sent (do NOT repeat any of these): {{ $json.previous_words }}",
        "options": {
          "systemMessage": "You are an expert language teacher and vocabulary coach. Generate a daily vocabulary lesson for a language learner.\n\nCRITICAL RULES:\n1. NEVER repeat any word from the 'Words Already Sent' list\n2. Generate exactly the number of words specified in 'Words to Generate'\n3. Match the difficulty level precisely:\n   - Beginner: very common everyday words (A1-A2 level)\n   - Elementary: common words with some complexity (B1 level)\n   - Intermediate: moderately advanced vocabulary (B2 level)\n   - Advanced: sophisticated, nuanced vocabulary (C1 level)\n   - Expert: rare, literary, academic, or technical vocabulary (C2 level)\n4. Match the topic focus if specified (Business, Technology, Medical, Academic, IELTS-TOEFL, GRE-GMAT, or General)\n5. Memory tips must be creative mnemonics \u2014 word associations, visual images, word roots, or rhymes\n6. Example sentences must use the word naturally in a realistic context\n7. Quiz questions must test comprehension (not just definitions) \u2014 include scenario-based questions\n\nReturn ONLY a valid JSON object \u2014 no markdown, no backticks, no explanation:\n{\n  \"language\": \"English\",\n  \"difficulty\": \"Intermediate\",\n  \"words\": [\n    {\n      \"word\": \"Ephemeral\",\n      \"pronunciation\": \"ih-FEM-er-ul\",\n      \"part_of_speech\": \"adjective\",\n      \"definition\": \"Lasting for a very short time; transitory\",\n      \"example_sentence\": \"The morning dew is ephemeral, vanishing as soon as the sun rises.\",\n      \"memory_tip\": \"Think of 'ephemeral' like an 'e-phase' \u2014 something that exists only in a brief phase before disappearing.\",\n      \"difficulty_tag\": \"Intermediate\"\n    }\n  ],\n  \"quiz\": [\n    {\n      \"question\": \"Which sentence uses 'ephemeral' correctly?\",\n      \"options\": [\n        \"A) His ephemeral friendship lasted over forty years.\",\n        \"B) The rainbow was ephemeral, fading within minutes.\",\n        \"C) She has an ephemeral personality that never changes.\",\n        \"D) The ephemeral mountain has stood for centuries.\"\n      ],\n      \"correct\": \"B\",\n      \"explanation\": \"Ephemeral means short-lived. Only option B describes something that vanishes quickly.\"\n    }\n  ],\n  \"motivational_message\": \"Every word you learn is a new lens through which you see the world. Keep going!\"\n}\n\nReturn ONLY the JSON object. Nothing before or after it."
        },
        "promptType": "define"
      },
      "typeVersion": 1.9
    },
    {
      "id": "e830304d-ceca-484d-9646-4e253d80ad54",
      "name": "OpenAI \u2014 GPT-4o-mini Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        928,
        -64
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "8191c720-bdc2-43ea-b71d-6ab8013cedd1",
      "name": "7. Code \u2014 Format Email and Words",
      "type": "n8n-nodes-base.code",
      "position": [
        1248,
        -272
      ],
      "parameters": {
        "jsCode": "const raw = $input.first().json.output || $input.first().json.text || '';\n\nlet vocab;\ntry {\n  const cleaned = raw.replace(/```json/gi, '').replace(/```/g, '').trim();\n  vocab = JSON.parse(cleaned);\n} catch (e) {\n  throw new Error('Could not parse vocabulary JSON from AI. Raw: ' + raw.slice(0, 300));\n}\n\nconst userCtx = $('5. Code \u2014 Prepare AI Prompt').item.json;\nconst userName      = userCtx.user_name || 'Learner';\nconst userEmail     = userCtx.user_email || '';\nconst language      = userCtx.language || 'English';\nconst difficulty    = userCtx.difficulty || 'Intermediate';\nconst totalLearned  = userCtx.total_learned || 0;\nconst words         = vocab.words || [];\nconst quiz          = vocab.quiz || [];\nconst motivational  = vocab.motivational_message || 'Keep learning every day!';\nconst today         = new Date().toLocaleDateString('en-US', { weekday:'long', month:'long', day:'numeric', year:'numeric' });\nconst todayISO      = new Date().toISOString().split('T')[0];\nconst dayNumber     = totalLearned + 1;\n\nconst esc = s => String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');\n\n// Color scheme per difficulty\nconst diffColors = {\n  'Beginner':     { accent: '#10b981', light: '#f0fdf4', badge: '#d1fae5', badgeText: '#065f46' },\n  'Elementary':   { accent: '#3b82f6', light: '#eff6ff', badge: '#dbeafe', badgeText: '#1e40af' },\n  'Intermediate': { accent: '#8b5cf6', light: '#f5f3ff', badge: '#ede9fe', badgeText: '#4c1d95' },\n  'Advanced':     { accent: '#f59e0b', light: '#fffbeb', badge: '#fef3c7', badgeText: '#78350f' },\n  'Expert':       { accent: '#ef4444', light: '#fef2f2', badge: '#fee2e2', badgeText: '#7f1d1d' }\n};\nconst c = diffColors[difficulty] || diffColors['Intermediate'];\n\n// Build word cards HTML\nlet wordCardsHtml = '';\nwords.forEach((w, i) => {\n  const highlighted = esc(w.example_sentence || '').replace(\n    new RegExp('\\\\b' + esc(w.word || '').replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&') + '\\\\b', 'gi'),\n    match => `<strong style=\"color:${c.accent};\">${match}</strong>`\n  );\n  wordCardsHtml += `\n  <div style=\"border:1px solid #e5e7eb;border-radius:10px;margin-bottom:16px;overflow:hidden;\">\n    <div style=\"background:${c.accent};padding:12px 16px;display:flex;justify-content:space-between;align-items:center;\">\n      <div>\n        <span style=\"color:#fff;font-size:20px;font-weight:800;letter-spacing:0.5px;\">${esc(w.word)}</span>\n        <span style=\"color:rgba(255,255,255,0.75);font-size:13px;margin-left:10px;\">${esc(w.pronunciation)}</span>\n      </div>\n      <span style=\"background:rgba(255,255,255,0.2);color:#fff;font-size:11px;padding:3px 10px;border-radius:20px;font-weight:600;\">${esc(w.part_of_speech)}</span>\n    </div>\n    <div style=\"padding:14px 16px;background:#fff;\">\n      <p style=\"font-size:13px;color:#374151;margin:0 0 10px;\"><strong style=\"color:#1f2937;\">Definition:</strong> ${esc(w.definition)}</p>\n      <div style=\"background:${c.light};border-left:3px solid ${c.accent};padding:10px 12px;border-radius:0 6px 6px 0;margin-bottom:10px;\">\n        <p style=\"font-size:13px;color:#374151;margin:0;\"><strong>Example:</strong> ${highlighted}</p>\n      </div>\n      <div style=\"background:#f9fafb;border-radius:6px;padding:10px 12px;\">\n        <p style=\"font-size:12px;color:#6b7280;margin:0;\">&#128161; <strong>Memory Tip:</strong> ${esc(w.memory_tip)}</p>\n      </div>\n    </div>\n  </div>`;\n});\n\n// Build quiz HTML\nlet quizHtml = '';\nif (quiz.length > 0) {\n  quizHtml = `\n  <div style=\"background:#1e3a5f;border-radius:10px;padding:20px;margin-top:24px;\">\n    <h3 style=\"color:#fff;font-size:15px;margin:0 0 16px;\">&#129488; Daily Quiz \u2014 Test Your Knowledge</h3>`;\n  quiz.forEach((q, qi) => {\n    quizHtml += `\n    <div style=\"background:rgba(255,255,255,0.05);border-radius:8px;padding:14px;margin-bottom:12px;\">\n      <p style=\"color:#e2e8f0;font-size:13px;font-weight:600;margin:0 0 10px;\">${qi+1}. ${esc(q.question)}</p>`;\n    (q.options || []).forEach(opt => {\n      const isCorrect = opt.trim().startsWith(q.correct + ')');\n      quizHtml += `<p style=\"color:${isCorrect ? '#86efac' : '#94a3b8'};font-size:12px;margin:4px 0;padding:6px 10px;background:${isCorrect ? 'rgba(134,239,172,0.1)' : 'transparent'};border-radius:4px;\">${esc(opt)}</p>`;\n    });\n    quizHtml += `<p style=\"color:#fbbf24;font-size:11px;margin:8px 0 0;\">&#10003; ${esc(q.explanation)}</p></div>`;\n  });\n  quizHtml += `</div>`;\n}\n\n// Build full HTML email\nconst htmlEmail = `<!DOCTYPE html>\n<html><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"></head>\n<body style=\"font-family:Arial,sans-serif;max-width:640px;margin:0 auto;background:#f0f4f8;padding:20px;\">\n\n  <div style=\"background:#1e3a5f;padding:24px 28px;border-radius:10px 10px 0 0;\">\n    <div style=\"font-size:11px;color:#93c5fd;letter-spacing:1.5px;text-transform:uppercase;margin-bottom:6px;\">Daily Vocabulary</div>\n    <h1 style=\"color:#fff;font-size:20px;margin:0 0 4px;\">${esc(language)} \u2014 Day ${dayNumber}</h1>\n    <p style=\"color:#93c5fd;font-size:12px;margin:0;\">${today} &nbsp;\u2022&nbsp; ${words.length} new words &nbsp;\u2022&nbsp; <span style=\"background:${c.badge};color:${c.badgeText};padding:2px 8px;border-radius:10px;font-weight:700;font-size:11px;\">${esc(difficulty)}</span></p>\n  </div>\n\n  <div style=\"background:#fff;padding:20px 24px;\">\n    <p style=\"font-size:13px;color:#6b7280;margin:0 0 20px;\">Hello ${esc(userName)}! Here are your ${words.length} words for today. ${totalLearned > 0 ? `You have learned <strong style=\"color:${c.accent};\">${totalLearned} words</strong> so far \u2014 keep it up!` : 'Welcome to your vocabulary journey!'}</p>\n\n    ${wordCardsHtml}\n    ${quizHtml}\n\n    <div style=\"background:#fffbeb;border-left:4px solid #f59e0b;padding:12px 16px;border-radius:0 6px 6px 0;margin-top:20px;\">\n      <p style=\"font-size:13px;color:#92400e;margin:0;\">&#127775; ${esc(motivational)}</p>\n    </div>\n  </div>\n\n  <div style=\"background:#1e3a5f;padding:12px 24px;border-radius:0 0 10px 10px;text-align:center;\">\n    <p style=\"color:#93c5fd;font-size:11px;margin:0;\">Vocabulary Builder &nbsp;\u2022&nbsp; Powered by n8n + GPT-4o-mini &nbsp;\u2022&nbsp; ${words.length} words today &nbsp;\u2022&nbsp; ${totalLearned + words.length} total</p>\n  </div>\n\n</body></html>`;\n\n// Prepare words to save to history\nconst wordsToSave = words.map(w => ({\n  'Date': todayISO,\n  'User Email': userEmail,\n  'Language': language,\n  'Word': w.word,\n  'Definition': w.definition,\n  'Example Sentence': w.example_sentence,\n  'Difficulty': w.difficulty_tag || difficulty\n}));\n\nreturn [{ json: {\n  user_email: userEmail,\n  user_name: userName,\n  language,\n  difficulty,\n  word_count: words.length,\n  words_to_save: wordsToSave,\n  html_email: htmlEmail,\n  email_subject: `Your ${language} Words for Today \u2014 Day ${dayNumber} (${difficulty})`\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "b8922fdf-aaa5-4411-aec0-b39a0cf1517e",
      "name": "8. Code \u2014 Spread Word Rows",
      "type": "n8n-nodes-base.code",
      "position": [
        1488,
        -272
      ],
      "parameters": {
        "jsCode": "// Spread words_to_save array into individual items for Sheets\n// Gmail uses $('7. Code \u2014 Format Email and Words').first() for email data\nconst data = $input.first().json;\nconst rows = data.words_to_save || [];\n\nif (rows.length === 0) {\n  throw new Error('No words to save. Check that AI generated vocabulary correctly.');\n}\n\nreturn rows.map(row => ({ json: row }));"
      },
      "typeVersion": 2
    },
    {
      "id": "db7906c8-2a29-40ae-a4e7-011a6727612c",
      "name": "9. Google Sheets \u2014 Append Word History",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1728,
        -272
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $json['Date'] }}",
            "Word": "={{ $json['Word'] }}",
            "Language": "={{ $json['Language'] }}",
            "Definition": "={{ $json['Definition'] }}",
            "Difficulty": "={{ $json['Difficulty'] }}",
            "User Email": "={{ $json['User Email'] }}",
            "Example Sentence": "={{ $json['Example Sentence'] }}"
          },
          "schema": [
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "User Email",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "User Email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Language",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Language",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Word",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Word",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Definition",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Definition",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Example Sentence",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Example Sentence",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Difficulty",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Difficulty",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Word History"
        },
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "bbeac503-1c00-4213-8989-a760b5045321",
      "name": "10. Gmail \u2014 Send Vocabulary Email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1968,
        -272
      ],
      "parameters": {
        "sendTo": "={{ $('7. Code \u2014 Format Email and Words').first().json.user_email }}",
        "message": "={{ $('7. Code \u2014 Format Email and Words').first().json.html_email }}",
        "options": {
          "senderName": "Vocabulary Builder"
        },
        "subject": "={{ $('7. Code \u2014 Format Email and Words').first().json.email_subject }}"
      },
      "typeVersion": 2.1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "c6b40173-4e5b-424d-a57e-30441517382b",
  "connections": {
    "8. Code \u2014 Spread Word Rows": {
      "main": [
        [
          {
            "node": "9. Google Sheets \u2014 Append Word History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI \u2014 GPT-4o-mini Model": {
      "ai_languageModel": [
        [
          {
            "node": "6. AI Agent \u2014 Generate Vocabulary",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "5. Code \u2014 Prepare AI Prompt": {
      "main": [
        [
          {
            "node": "6. AI Agent \u2014 Generate Vocabulary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Schedule \u2014 Every Day at 7 AM": {
      "main": [
        [
          {
            "node": "2. Google Sheets \u2014 Read User Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7. Code \u2014 Format Email and Words": {
      "main": [
        [
          {
            "node": "8. Code \u2014 Spread Word Rows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6. AI Agent \u2014 Generate Vocabulary": {
      "main": [
        [
          {
            "node": "7. Code \u2014 Format Email and Words",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2. Google Sheets \u2014 Read User Config": {
      "main": [
        [
          {
            "node": "3. SplitInBatches \u2014 One User at a Time",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Google Sheets \u2014 Read Word History": {
      "main": [
        [
          {
            "node": "5. Code \u2014 Prepare AI Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. SplitInBatches \u2014 One User at a Time": {
      "main": [
        [
          {
            "node": "4. Google Sheets \u2014 Read Word History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "9. Google Sheets \u2014 Append Word History": {
      "main": [
        [
          {
            "node": "10. Gmail \u2014 Send Vocabulary Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Fill in a form with your exam date, subjects, daily available hours, and study start time, and this workflow generates a complete personalised day-by-day study schedule using GPT-4o-mini, adds every session to Google Calendar automatically, logs the full plan to Google Sheets,…

Source: https://n8n.io/workflows/16058/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

This n8n automation workflow automates the creation, scripting, production, and posting of YouTube videos. It leverages AI (OpenAI), image generation (PIAPI), video rendering (Shotstack), and platform

Agent, OpenAI Chat, Airtable Tool +7
AI & RAG

Created by: Peyton Leveillee Last updated: October 2025

OpenAI Chat, Google Sheets, HTTP Request +5
AI & RAG

The Multi-Model Agency Content Engine is a high-performance editorial system designed for agencies. It solves the "blank page" problem by alternating between real-world social proof and strategic expe

Google Sheets, Gmail, Google Drive +6
AI & RAG

This workflow automates the creation, rendering, approval, and posting of TikTok-style POV (Point of View) videos to Instagram, with cross-posting to Facebook and YouTube. It eliminates manual video p

OpenAI Chat, Output Parser Item List, HTTP Request +10
AI & RAG

Generate SEO-friendly WordPress blog drafts from a Google Sheets queue using OpenAI. This workflow writes full HTML blog content, selects a relevant Pexels featured image with a second AI Agent, uploa

HTTP Request, Agent, Google Sheets +4