AutomationFlowsAI & RAG › LINE Handwritten Memos to Tagged Notes with Gemini

LINE Handwritten Memos to Tagged Notes with Gemini

Original n8n title: Convert Line Handwritten Memo Images to Tagged, Searchable Notes with Gemini, Google Drive and Google Sheets

ByHiroshi Hashimoto @hashimoto on n8n.io

This workflow converts handwritten memo images sent via LINE into structured, searchable knowledge using AI.Users simply send a handwritten memo photo. The workflow automatically performs OCR, summarizes the content, generates tags, and stores the results in Google Sheets.Images…

Webhook trigger★★★★★ complexityAI-powered36 nodesHTTP RequestGoogle DriveChain LlmGoogle Gemini ChatGoogle Sheets
AI & RAG Trigger: Webhook Nodes: 36 Complexity: ★★★★★ AI nodes: yes Added:

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

This workflow follows the Chainllm → Google Drive 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": "VBAdlukzjLaemKYa",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "LINE AI Handwritten Memo OCR & Tag Search System",
  "tags": [],
  "nodes": [
    {
      "id": "c63563b3-178e-43a6-8313-2662d4f582ec",
      "name": "LINE_Receive_Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -352,
        -352
      ],
      "parameters": {
        "path": "=e89b4943-1f2e-4d37-84ad-0fce0b78175e",
        "options": {},
        "httpMethod": "=POST"
      },
      "typeVersion": 2.1
    },
    {
      "id": "2485ae25-d2b6-4c64-91a5-18b83f160482",
      "name": "LINE_Download_ImageContent",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        880,
        -368
      ],
      "parameters": {
        "url": "=https://api-data.line.me/v2/bot/message/{{ $('LINE_Receive_Webhook').item.json.body.events[0].message.id }}/content",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "=Authorization",
              "value": "=Bearer {{ $('Config_Set_Environment').item.json.LINE_ACCESS_TOKEN }}"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "7e255b30-152a-4a7a-904e-309c6ac4be42",
      "name": "Drive_Upload_Image",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        1168,
        -368
      ],
      "parameters": {
        "name": "={{ $('LINE_Receive_Webhook').item.json.body.events[0].message.id }}.jpg",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive",
          "cachedResultUrl": "https://drive.google.com/drive/my-drive",
          "cachedResultName": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "list",
          "value": "1xR4KZbVauD6_J0t5YbPBvFSS1dxPLuWu",
          "cachedResultUrl": "https://drive.google.com/drive/folders/1xR4KZbVauD6_J0t5YbPBvFSS1dxPLuWu",
          "cachedResultName": "LINE_PIC"
        }
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "e1486120-4929-4ecb-a838-c1cffdcbfa45",
      "name": "AI_OCR_Analyze_Image",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        1568,
        -368
      ],
      "parameters": {
        "text": "=You are a high-precision OCR-dedicated engine.\n\nRead the text in the image as accurately as possible.\nIf the text cannot be read, always output the following JSON and terminate the analysis:\n{\n  \"title\": \"\",\n  \"summary\": \"Unable to read the text.\",\n  \"tags\":[]\n}\n\nIf the text is readable, create the following based on a summarized version of the content:\n\nTitle\nSummary\nTags (up to three, important keywords)\n\nRules:\n\nCategory should be a short, easily summarized word.\nSummary should be readable and within 200 characters.\nTags should be single words, with no duplicates.\nAvoid overly abstract tags (e.g., important, memo).\n\nImportant:\n\nOutput only pure JSON.\nDo not include any explanations or preambles.\nAbsolutely do not output any text other than JSON.\n\nOutput the following items in JSON format:\n{\n  \"title\": \"\",\n  \"summary\": \"\",\n  \"tags\":[\"\",\"\",\"\"]\n}\n\n",
        "batching": {},
        "messages": {
          "messageValues": [
            {
              "type": "HumanMessagePromptTemplate",
              "messageType": "imageBinary"
            }
          ]
        },
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "b58cc94f-97cd-4857-87c1-b312ebb9768e",
      "name": "AI_Model_Gemini",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1568,
        -208
      ],
      "parameters": {
        "options": {
          "topP": 1,
          "temperature": 0,
          "maxOutputTokens": 2048
        }
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2a32d0b4-2de6-461e-a432-0b5be1942010",
      "name": "Data_Parse_OCR_JSON",
      "type": "n8n-nodes-base.code",
      "position": [
        1984,
        -368
      ],
      "parameters": {
        "jsCode": "let raw = $json[\"text\"] || \"\";\n\n// ```Remove JSON\nraw = raw.replace(/```json\\s*/i, \"\").replace(/```/g, \"\").trim();\n\n// Extract only the JSON part.\nconst match = raw.match(/\\{[\\s\\S]*\\}/);\nif (match) {\n  raw = match[0];\n}\n\n// Parse\nlet parsed;\ntry {\n  parsed = JSON.parse(raw);\n} catch (e) {\n  parsed = {\n    title: \"\",\n    summary: raw.slice(0, 100),\n    tags: []\n  };\n}\n\n// title\nif (!parsed.title || typeof parsed.title !== \"string\") {\n  parsed.title = \"NoTitle\";\n}\n\n// summary\nif (!parsed.summary || typeof parsed.summary !== \"string\") {\n  parsed.summary = \"NoSummary\";\n}\n\n// tags\nif (!Array.isArray(parsed.tags)) {\n  parsed.tags = [];\n}\n\n// Sanitize the contents of the tags as well\nparsed.tags = parsed.tags\n  .filter(tag => typeof tag === \"string\")\n  .slice(0, 3);\n\n// Original item\nconst item = $input.first();\n\n// Merge\nitem.json = {\n  ...item.json,\n  ...parsed\n};\n\n// Keep binary\nitem.binary = $input.first().binary;\n\nreturn [item];"
      },
      "typeVersion": 2
    },
    {
      "id": "7c8afb53-4d68-4855-b9ad-d666066b8092",
      "name": "AI_Check_OCR_Failure",
      "type": "n8n-nodes-base.if",
      "position": [
        2208,
        -368
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "0cad7233-7daa-4bff-bde7-08f254669175",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.summary }}",
              "rightValue": "=Unable to read the text."
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "a64dd875-873d-4af2-ad8c-bc928603bf0c",
      "name": "LINE_Reply_NoImage",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        720,
        560
      ],
      "parameters": {
        "url": "=https://api.line.me/v2/bot/message/reply",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"replyToken\": \"{{$node['LINE_Receive_Webhook'].json.body.events[0].replyToken }}\",\n  \"messages\": [\n    {\n      \"type\": \"text\",\n      \"text\": \"If you send a handwritten memo, I will summarize its contents.\nYou can also search for memos containing a specific tag using #tagname.\nUse #taglist to display the list of tags.\"\n    }\n  ]\n}\n",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "=Authorization",
              "value": "=Bearer {{$node[\"Config_Set_Environment\"].json.LINE_ACCESS_TOKEN }}"
            },
            {
              "name": "=Content-type",
              "value": "=application/json"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "f6c5cf0f-764b-4a8a-88dd-f0e918897c04",
      "name": "Sheets_Append_Row",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2656,
        -352
      ],
      "parameters": {
        "columns": {
          "value": {
            "date": "={{$now.format(\"yyyy-MM-dd HH:mm:ss\")}}",
            "tags": "={{ $json.tags.join(\", \") }}",
            "title": "={{ $json.title }}",
            "webUrl": "={{ $('Drive_Upload_Image').item.json.webViewLink }}",
            "summary": "={{ $json.summary }}"
          },
          "schema": [
            {
              "id": "date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "title",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "summary",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "summary",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "webUrl",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "webUrl",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "tags",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "tags",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1eFUe4xD5Xgd5BbQ2kT_QzKQzF3EspnuXdpYYv8p46HM/edit#gid=0",
          "cachedResultName": "template"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1eFUe4xD5Xgd5BbQ2kT_QzKQzF3EspnuXdpYYv8p46HM",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1eFUe4xD5Xgd5BbQ2kT_QzKQzF3EspnuXdpYYv8p46HM/edit?usp=drivesdk",
          "cachedResultName": "OCR_data"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "802e8d5c-49bb-4af0-a950-73cd2160f110",
      "name": "LINE_Push_Completion_Message",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3024,
        -352
      ],
      "parameters": {
        "url": "=https://api.line.me/v2/bot/message/push",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"to\":\"{{ $('LINE_Receive_Webhook').item.json.body.events[0].source.userId }}\",\n  \"messages\": [\n    {\n      \"type\": \"text\",\n      \"text\": \"The text has been summarized and saved. The title is\u300c{{$json.title}}\u300d,and the tags are\u300c{{ $json.tags }}\u300d\\nThe summary is\u300c{{ $json.summary }}\u300d\\n\\nYou can search for notes containing a specific tag using #tagname.\\nUse #taglist to display the list of tags.\"\n    }\n  ]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "=Authorization",
              "value": "=Bearer {{ $('Config_Set_Environment').item.json.LINE_ACCESS_TOKEN }}"
            },
            {
              "name": "=Content-type",
              "value": "=application/json"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "b8d79321-ca8b-402c-8606-c262dab9af48",
      "name": "Config_Set_Environment",
      "type": "n8n-nodes-base.set",
      "position": [
        -128,
        -352
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "aa8b0c5c-df21-49f6-a885-eff8ad142d3d",
              "name": "LINE_ACCESS_TOKEN",
              "type": "string",
              "value": "=YOUR_ACCESS_TOKEN"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "01d5539d-d2c5-40b9-84a1-abf4696cfa4a",
      "name": "LINE_Reply_Processing",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        672,
        -368
      ],
      "parameters": {
        "url": "=https://api.line.me/v2/bot/message/reply",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"replyToken\": \"{{$node['LINE_Receive_Webhook'].json.body.events[0].replyToken }}\",\n  \"messages\": [\n    {\n      \"type\": \"text\",\n      \"text\": \"Processing\u2026 please wait a moment.\"\n    }\n  ]\n}\n",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "=Authorization",
              "value": "=Bearer {{$node[\"Config_Set_Environment\"].json.LINE_ACCESS_TOKEN }}"
            },
            {
              "name": "=Content-type",
              "value": "=application/json"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "76592edc-f37d-47e0-bfe4-77caec17ca67",
      "name": "LINE_Push_NoSummary",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2400,
        -480
      ],
      "parameters": {
        "url": "=https://api.line.me/v2/bot/message/push",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n  \"to\":\"{{ $('LINE_Receive_Webhook').item.json.body.events[0].source.userId }}\",\n  \"messages\": [\n    {\n      \"type\": \"text\",\n      \"text\": \"The image could not be read.\\n\u30fbThe text may be too small.\\n\u30fbPlease retake the photo in a well-lit area.\"\n    }\n  ]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "=Authorization",
              "value": "=Bearer {{ $('Config_Set_Environment').item.json.LINE_ACCESS_TOKEN }}"
            },
            {
              "name": "=Content-type",
              "value": "=application/json"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "d339fe26-0f34-4838-9b2f-45583bfb12f7",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        112,
        -608
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 400,
        "content": "## Detect message type\n\nThe workflow checks whether the incoming message is:\n\n\u2022 Image (handwritten memo)\n\u2022 Text command\n\nImage messages trigger the OCR pipeline.\n\nText messages are treated as commands such as tag search."
      },
      "typeVersion": 1
    },
    {
      "id": "3e7d1cd1-c2eb-4394-9a19-9db8a68e8a04",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1216,
        -672
      ],
      "parameters": {
        "width": 752,
        "height": 1440,
        "content": "## LINE AI Handwritten Memo OCR & Tag Search System\n\nThis workflow converts handwritten memo images sent via LINE into structured, searchable knowledge using AI.Users simply send a handwritten memo photo. The workflow automatically performs OCR, summarizes the content, generates tags, and stores the results in Google Sheets.Images are archived in Google Drive, allowing easy access to the original memo later.\n\n## Main Features\n\n\u2022 AI OCR for handwritten memo recognition  \n\u2022 Automatic title and summary generation  \n\u2022 Tag extraction for knowledge organization  \n\u2022 Image archiving in Google Drive  \n\u2022 Structured storage in Google Sheets  \n\u2022 Tag-based memo search via LINE  \n\u2022 Tag list generation for easy navigation\n\n## User Commands (via LINE)\n\nSend an image  \n\u2192 The memo is analyzed, summarized, tagged, and saved automatically.\n\n#tagname  \n\u2192 Searches memos that contain the specified tag.\n\n#taglist\n\u2192 Displays all tags currently stored in the database.\n\n## Workflow Steps\n\n1. Receive message from LINE via Webhook  \n2. Validate message type (image or command)  \n3. Save image to Google Drive  \n4. Run AI OCR and generate structured JSON  \n5. Parse and validate AI output safely  \n6. Store memo data in Google Sheets  \n7. Send completion message to the user\n\nIf OCR fails, the user receives guidance to retake the photo.\n\nStored Data Structure\n\nEach memo record includes:\n\n\u2022 Title  \n\u2022 Summary  \n\u2022 Tags  \n\u2022 Timestamp  \n\u2022 Link to the original image\n\n## Setup Requirements Before using this workflow:\n\n1. Create a LINE Messaging API channel\n2. Obtain a Channel Access Token\n3. Prepare a Google Sheet for memo storage\n4. Prepare a Google Drive folder for image storage\n5. Set credentials in the Config node\n\u30fbLINE API\n\n## Notes\n\n\u2022 OCR accuracy may vary depending on handwriting quality  \n\u2022 Very small or unclear text may not be recognized  \n\u2022 The workflow is optimized for quick memo capture and organization\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "e93c4693-92f5-42d8-8bb3-d423a7f834ac",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -416,
        -640
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 480,
        "content": "## Receive message from LINE \nThis workflow starts when a user sends a message to the LINE bot. \nThe LINE Messaging API sends an event to the webhook endpoint in n8n.\n\n The event contains:\n \u2022 userId \n \u2022 message type (image or text)\n \u2022 messageId\n\n This information is used to determine how the workflow should proceed."
      },
      "typeVersion": 1
    },
    {
      "id": "d1209241-8ef0-4fb2-80dd-f88c2684f6d4",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        608,
        -608
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 400,
        "content": "## Retrieve image from LINE\n\nIf the message is an image, the workflow retrieves the image file\nusing the LINE Messaging API.\n\nThe image binary data will be used for OCR processing and stored later."
      },
      "typeVersion": 1
    },
    {
      "id": "cf049c97-084e-48d3-aab5-b58dafb094cc",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1136,
        -608
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 400,
        "content": "## Save image to Google Drive\n\nThe memo image is stored in Google Drive for long-term access.\n\nThis allows users to open the original handwritten memo later\nfrom the stored link."
      },
      "typeVersion": 1
    },
    {
      "id": "f447bbfc-fbb7-4444-bd60-d4f375623776",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1536,
        -640
      ],
      "parameters": {
        "color": 7,
        "width": 368,
        "height": 560,
        "content": "## AI OCR processing\n\nThe uploaded image is analyzed using AI OCR.\n\nThe AI extracts:\n\n\u2022 memo title\n\u2022 summary\n\u2022 tags\n\u2022 main text\n\nThe result is returned as structured JSON."
      },
      "typeVersion": 1
    },
    {
      "id": "8c99d817-de3d-496b-b5ea-449d82e867cd",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1920,
        -640
      ],
      "parameters": {
        "color": 7,
        "width": 656,
        "height": 448,
        "content": "## Parse AI response\n\nThe JSON response from the AI is validated and parsed.\n\nThis step ensures the workflow receives structured data\nbefore saving it into the database."
      },
      "typeVersion": 1
    },
    {
      "id": "b0a1c12c-f0be-47eb-9cac-9c30dadc8d97",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2592,
        -608
      ],
      "parameters": {
        "color": 7,
        "width": 336,
        "height": 416,
        "content": "## Store memo data\n\nThe memo data is stored in Google Sheets.\n\nEach record contains:\n\n\u2022 title\n\u2022 summary\n\u2022 tags\n\u2022 timestamp\n\u2022 image link."
      },
      "typeVersion": 1
    },
    {
      "id": "d7767fd5-8c2d-4dd2-8afe-ee2a062d8c2d",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2944,
        -528
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 352,
        "content": "## Send confirmation message\n\nAfter the memo is successfully processed and stored,\na confirmation message is sent to the user via LINE.\n\nThe message confirms that the memo has been saved."
      },
      "typeVersion": 1
    },
    {
      "id": "512ee1b5-34a6-460c-9ac7-328ca4758a08",
      "name": "Extract_Tag",
      "type": "n8n-nodes-base.code",
      "position": [
        720,
        400
      ],
      "parameters": {
        "jsCode": "const text = $('LINE_Receive_Webhook').first().json.body.events[0].message.text;\n\n// #delete\nconst tag = text.replace(\"#\",\"\").trim();\n\nreturn [\n{\njson:{\ntag:tag,\n}\n}\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "9c13a744-ba39-4936-adb3-fd9cb7269856",
      "name": "Filter_By_Tag",
      "type": "n8n-nodes-base.code",
      "position": [
        1088,
        400
      ],
      "parameters": {
        "jsCode": "const searchTag = $items(\"Extract_Tag\")[0].json.tag;\n\nconst rows = $input.all();\n\nconst results = rows.filter(row => {\n\n  const tags = row.json.tags || \"\";\n\n  return tags.toLowerCase().includes(searchTag.toLowerCase());\n\n});\n\nreturn results.slice(0,5);"
      },
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "ec60432f-ef68-4ca9-aa4f-5e3e3fd0d14d",
      "name": "Build_Search_Result",
      "type": "n8n-nodes-base.code",
      "position": [
        1248,
        400
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\n\n// Keep only the items that actually have data\nconst realItems = items.filter(item => item.json && (item.json.title || item.json.summary || (item.json.tags && item.json.tags.length)));\n\nlet payload;\n\nif (realItems.length === 0) {\n  payload = { message: \"No matching notes were found.\\\\nSend #taglist to see a list of available tags.\" };\n} else {\n  let text = \"Tag search results\\n\\n\";\n\n  realItems.forEach((item, i) => {\n    text += (i + 1) + \"\\n\";\n    text += \"Title:\" + (item.json.title || \"NoTitle\") + \"\\n\";\n    text += \"Summary:\" + (item.json.summary || \"NoSummary\") + \"\\n\";\n    text += \"tags:\" + (Array.isArray(item.json.tags) ? item.json.tags.join(\", \") : item.json.tags || \"\") + \"\\n\\n\";\n  });\n\n  payload = { message: text };\n}\n\nreturn [{ json: payload }];"
      },
      "typeVersion": 2
    },
    {
      "id": "39f0f8a1-9594-41e3-be33-49d84328ce82",
      "name": "LINE_Push_TagResults",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1440,
        400
      ],
      "parameters": {
        "url": "=https://api.line.me/v2/bot/message/push",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({\n  to: $('LINE_Receive_Webhook').item.json.body.events[0].source.userId,\n  messages: [\n    {\n      type: \"text\",\n      text: $json.message\n    }\n  ]\n}) }}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "=Authorization",
              "value": "=Bearer {{$node[\"Config_Set_Environment\"].json.LINE_ACCESS_TOKEN}}"
            },
            {
              "name": "=Content-Type",
              "value": "=application/json"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "41507112-15c6-46c2-bad4-b4b010426457",
      "name": "Sheets_Get_All_Memos",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        896,
        400
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1eFUe4xD5Xgd5BbQ2kT_QzKQzF3EspnuXdpYYv8p46HM/edit#gid=0",
          "cachedResultName": "template"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1eFUe4xD5Xgd5BbQ2kT_QzKQzF3EspnuXdpYYv8p46HM",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1eFUe4xD5Xgd5BbQ2kT_QzKQzF3EspnuXdpYYv8p46HM/edit?usp=drivesdk",
          "cachedResultName": "OCR_data"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "b387017c-8dd2-40bc-b761-59898ca90d42",
      "name": "Restore_Image_Binary",
      "type": "n8n-nodes-base.code",
      "position": [
        1360,
        -368
      ],
      "parameters": {
        "jsCode": "const item = $input.first();\n\n// Restore binary\nitem.binary = $node[\"LINE_Download_ImageContent\"].binary;\n\nreturn [item];"
      },
      "typeVersion": 2
    },
    {
      "id": "faf8aece-42ef-4ced-92be-645eac3a09ab",
      "name": "Check_Image_Message",
      "type": "n8n-nodes-base.if",
      "position": [
        176,
        -352
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "58a4dbd8-8eed-400c-b94a-7083131e133a",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $('LINE_Receive_Webhook').item.json.body.events[0].message.type }}",
              "rightValue": "image"
            },
            {
              "id": "69e0a667-2629-4538-9bb6-518274328f8b",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "9d207368-4fb8-4a1e-941d-737f0af98b3c",
      "name": "Check_Tag_Command",
      "type": "n8n-nodes-base.if",
      "position": [
        528,
        416
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "a73b3ed7-0dbb-489a-9ed6-fdb55b4b9d3d",
              "operator": {
                "type": "string",
                "operation": "startsWith"
              },
              "leftValue": "={{ $('LINE_Receive_Webhook').item.json.body.events[0].message.text }}",
              "rightValue": "=#"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "50369319-eda2-4c0f-a7c3-350535c1274a",
      "name": "Check_Tag_List_Command",
      "type": "n8n-nodes-base.if",
      "position": [
        336,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "bfbc6b45-7602-4be0-a7a6-790755851292",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $('LINE_Receive_Webhook').item.json.body.events[0].message.text }}",
              "rightValue": "#taglist"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "c3962197-9330-45ce-b4ce-1478b37020d7",
      "name": "Build_TagList",
      "type": "n8n-nodes-base.code",
      "position": [
        688,
        -16
      ],
      "parameters": {
        "jsCode": "const rows = $input.all();\n\nlet tagSet = new Set();\n\nfor (const item of rows) {\n\n  const tags = item.json.tags || \"\";\n\n  tags.split(\",\")\n    .map(t => t.trim())\n    .filter(t => t !== \"\")\n    .forEach(t => tagSet.add(t));\n\n}\n\nconst tagList = Array.from(tagSet).sort();\n\nreturn [\n{\njson:{\ntagList: tagList,\ntagText: tagList.join(\"\\\\n\")\n}\n}\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "71d3cc39-0879-4e50-a36c-4a7276b34e19",
      "name": "LINE_Push_TagList",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        848,
        -16
      ],
      "parameters": {
        "url": "=https://api.line.me/v2/bot/message/push",
        "method": "POST",
        "options": {},
        "jsonBody": "={\n \"to\": \"{{ $('LINE_Receive_Webhook').first().json.body.events[0].source.userId }}\",\n \"messages\": [\n  {\n   \"type\": \"text\",\n  \"text\": \"{{ '\u3010Taglist\u3011\\\\n' + $json.tagText }}\"\n   \n  }\n ]\n}",
        "sendBody": true,
        "sendHeaders": true,
        "specifyBody": "json",
        "headerParameters": {
          "parameters": [
            {
              "name": "=Authorization",
              "value": "=Bearer {{$node[\"Config_Set_Environment\"].json.LINE_ACCESS_TOKEN}}"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "aeadf7a0-5214-4342-941c-5d85ec18bc5f",
      "name": "Sheets_Get_All_Memos_For_TagList",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        528,
        -16
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1eFUe4xD5Xgd5BbQ2kT_QzKQzF3EspnuXdpYYv8p46HM/edit#gid=0",
          "cachedResultName": "template"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1eFUe4xD5Xgd5BbQ2kT_QzKQzF3EspnuXdpYYv8p46HM",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1eFUe4xD5Xgd5BbQ2kT_QzKQzF3EspnuXdpYYv8p46HM/edit?usp=drivesdk",
          "cachedResultName": "OCR_data"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "2f114e7c-3479-407e-bdab-744b523f3bd4",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        176
      ],
      "parameters": {
        "color": 7,
        "width": 1104,
        "height": 560,
        "content": "## Tag search command\n\nUsers can search memos by sending a tag command.\n\nExample\n#study\n#meeting\n\nThe workflow filters memos that contain the specified tag\nand returns the results to the user."
      },
      "typeVersion": 1
    },
    {
      "id": "24ace017-7bea-4a40-bcfa-82ee564de1aa",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        288,
        -192
      ],
      "parameters": {
        "color": 7,
        "width": 768,
        "height": 352,
        "content": "## Generate tag list\n\nThe workflow collects all stored tags\nand generates a unique tag list.\n\nThis helps users quickly see which topics\nare available in the memo database."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "c1c19ead-5e47-40d7-96af-79f1610b27f8",
  "connections": {
    "Extract_Tag": {
      "main": [
        [
          {
            "node": "Sheets_Get_All_Memos",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build_TagList": {
      "main": [
        [
          {
            "node": "LINE_Push_TagList",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter_By_Tag": {
      "main": [
        [
          {
            "node": "Build_Search_Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI_Model_Gemini": {
      "ai_languageModel": [
        [
          {
            "node": "AI_OCR_Analyze_Image",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Check_Tag_Command": {
      "main": [
        [
          {
            "node": "Extract_Tag",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "LINE_Reply_NoImage",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets_Append_Row": {
      "main": [
        [
          {
            "node": "LINE_Push_Completion_Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Drive_Upload_Image": {
      "main": [
        [
          {
            "node": "Restore_Image_Binary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build_Search_Result": {
      "main": [
        [
          {
            "node": "LINE_Push_TagResults",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check_Image_Message": {
      "main": [
        [
          {
            "node": "LINE_Reply_Processing",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Check_Tag_List_Command",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Data_Parse_OCR_JSON": {
      "main": [
        [
          {
            "node": "AI_Check_OCR_Failure",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI_Check_OCR_Failure": {
      "main": [
        [
          {
            "node": "LINE_Push_NoSummary",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Sheets_Append_Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI_OCR_Analyze_Image": {
      "main": [
        [
          {
            "node": "Data_Parse_OCR_JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LINE_Receive_Webhook": {
      "main": [
        [
          {
            "node": "Config_Set_Environment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Restore_Image_Binary": {
      "main": [
        [
          {
            "node": "AI_OCR_Analyze_Image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets_Get_All_Memos": {
      "main": [
        [
          {
            "node": "Filter_By_Tag",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LINE_Reply_Processing": {
      "main": [
        [
          {
            "node": "LINE_Download_ImageContent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check_Tag_List_Command": {
      "main": [
        [
          {
            "node": "Sheets_Get_All_Memos_For_TagList",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Check_Tag_Command",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config_Set_Environment": {
      "main": [
        [
          {
            "node": "Check_Image_Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LINE_Download_ImageContent": {
      "main": [
        [
          {
            "node": "Drive_Upload_Image",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets_Get_All_Memos_For_TagList": {
      "main": [
        [
          {
            "node": "Build_TagList",
            "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

This workflow converts handwritten memo images sent via LINE into structured, searchable knowledge using AI.Users simply send a handwritten memo photo. The workflow automatically performs OCR, summarizes the content, generates tags, and stores the results in Google Sheets.Images…

Source: https://n8n.io/workflows/14502/ — 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

ANIS_HUB 1. Uses gmail, googleDrive, googleSheets, httpRequest. Webhook trigger; 89 nodes.

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

Resume Screening & Behavioral Interviews with Gemini, Elevenlabs, & Notion ATS copy. Uses outputParserStructured, chainLlm, googleDrive, stickyNote. Webhook trigger; 67 nodes.

Output Parser Structured, Chain Llm, Google Drive +9
AI & RAG

Candidate Engagement | Resume Screening | AI Voice Interviews | Applicant Insights

Output Parser Structured, Chain Llm, Google Drive +9
AI & RAG

Categories: Accounting Automation • OCR Processing • AI Data Extraction • Business Tools

HTTP Request, OpenRouter Chat, Google Gemini Chat +4
AI & RAG

This workflow receives handwritten memo images sent via LINE and automatically extracts, summarizes, and organizes the content using AI. User sends a handwritten memo image via LINE Webhook receives t

HTTP Request, Google Drive, Chain Llm +2