AutomationFlowsAI & RAG › WhatsApp to Google Drive AI Travel Journal

WhatsApp to Google Drive AI Travel Journal

Original n8n title: Create AI Travel Journal Stories From Whatsapp Using Claude and Google Drive

ByOneclick AI Squad @oneclick-ai on n8n.io

Automatically converts your daily WhatsApp messages and photos from travels into beautifully structured travel stories, saved as documents in Google Drive. Receive WhatsApp Updates - Webhook captures messages, photos, and locations from your travel day Validate & Aggregate…

Webhook trigger★★★★☆ complexityAI-powered18 nodesGoogle DriveAgentAnthropic ChatHTTP RequestEmail Send
AI & RAG Trigger: Webhook Nodes: 18 Complexity: ★★★★☆ AI nodes: yes Added:

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

This workflow follows the Agent → Emailsend 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": "DYwrdXRepewlWcH1",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "AI Travel Memory Journal Generator - WhatsApp to Drive Story",
  "tags": [],
  "nodes": [
    {
      "id": "a9b2ea33-0b60-4c8e-b2b7-7aad386187bf",
      "name": "Main Documentation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        -384
      ],
      "parameters": {
        "width": 820,
        "height": 1704,
        "content": "## AI Travel Memory Journal Generator\n\nAutomatically converts your daily WhatsApp messages and photos from travels into beautifully structured travel stories, saved as documents in Google Drive.\n\n### How it works\n\n1. **Receive WhatsApp Updates** - Webhook captures messages, photos, and locations from your travel day\n2. **Validate & Aggregate Content** - JavaScript organizes messages by day, extracts metadata, validates media\n3. **Fetch Previous Entries** - Retrieves existing journal from Google Drive for context and continuity\n4. **Prepare AI Context** - JavaScript builds comprehensive prompt with photos, messages, locations, and timeline\n5. **Claude AI Story Generation** - Transforms raw messages into narrative travel journal with insights\n6. **Parse & Format Story** - JavaScript structures the output into readable document format\n7. **Wait for Finalization** - Brief pause to ensure all processing completes\n8. **Save to Google Drive** - Creates or updates your travel journal document\n9. **Send Confirmation** - WhatsApp notification with preview of generated story\n10. **Respond to Webhook** - Returns success confirmation\n\n### Setup Steps\n\n1. Import workflow into n8n\n2. Configure credentials:\n   - **Anthropic API** - Claude AI for story generation\n   - **Google Drive** - Document storage and retrieval\n   - **WhatsApp Business API** or **Twilio WhatsApp** - Message integration\n3. Create a Google Drive folder for your travel journals\n4. Set up WhatsApp webhook integration:\n   - Point WhatsApp webhook to: `https://your-n8n-instance.com/webhook/travel-journal`\n   - Configure to send: messages, media, locations\n5. Update the \"Fetch Previous Journal\" node with your Drive folder ID\n6. Activate the workflow\n\n### Sample WhatsApp Input\n\n**Messages throughout the day:**\n- 09:30 AM: \"Just arrived in Kyoto! The train station architecture is stunning \ud83d\ude84\"\n- 11:45 AM: \"Fushimi Inari shrine - thousands of orange torii gates going up the mountain\"\n- \ud83d\udcf8 Photo: Torii gates pathway\n- 02:15 PM: \"Tried okonomiyaki for lunch. Amazing! The chef made it right in front of us\"\n- \ud83d\udcf8 Photo: Okonomiyaki cooking\n- 05:30 PM: \"Gion district at sunset. Spotted two geishas!\"\n- \ud83d\udccd Location: Gion, Kyoto, Japan\n- 08:45 PM: \"Dinner at an izakaya. Made friends with locals who taught us drinking games \ud83d\ude04\"\n\n### Generated Journal Output\n\n**Day 3: Kyoto - Ancient Temples and Modern Connections**\n\nThe day began with anticipation as the shinkansen pulled into Kyoto Station at 9:30 AM. The station itself was an architectural marvel\u2014a blend of traditional Japanese aesthetics and contemporary design that set the tone for what would be an unforgettable day.\n\nBy mid-morning, I found myself at Fushimi Inari Taisha, one of Kyoto's most iconic sites. The seemingly endless tunnel of vermillion torii gates created a mesmerizing pathway up Mount Inari. Each gate, donated by individuals and businesses, bore inscriptions in black kanji. The experience was both spiritual and surreal\u2014the way light filtered through the gates, creating dancing shadows on the stone path...\n\n[Full narrative continues with integrated photos, locations, and emotional insights]\n\n### Features\n- **Smart Aggregation** - Groups messages by day, even across time zones\n- **Photo Integration** - Embeds images inline with contextual descriptions\n- **Location Awareness** - Maps locations and adds geographical context\n- **Narrative Style** - Converts casual messages into polished travel prose\n- **Emotional Intelligence** - Captures mood and significance beyond literal text\n- **Timeline Coherence** - Maintains chronological flow and story arc\n- **Automatic Continuity** - Links to previous days for multi-day trip journals\n- **Format Flexibility** - Outputs as Google Docs with proper formatting\n\n### Privacy & Data\n- Messages are processed in real-time and not stored long-term\n- Photos are referenced but can be embedded or linked based on preference\n- Journal documents are private in your Google Drive\n- No message content is retained after journal generation"
      },
      "typeVersion": 1
    },
    {
      "id": "141c7e92-e8ac-46f5-b259-bc14afb4aa03",
      "name": "Section 1 - Input Processing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        152
      ],
      "parameters": {
        "color": 5,
        "width": 480,
        "height": 360,
        "content": "## 1. WhatsApp Input & Validation"
      },
      "typeVersion": 1
    },
    {
      "id": "79206477-ca0f-4117-9459-68738620585b",
      "name": "Section 2 - Context Building",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1544,
        -20
      ],
      "parameters": {
        "color": 5,
        "width": 880,
        "height": 740,
        "content": "## 2. Journal Context & AI Processing"
      },
      "typeVersion": 1
    },
    {
      "id": "1c179b32-2d4f-408c-8bef-1c1e9c29b838",
      "name": "Section 3 - Storage & Delivery",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2486,
        -76
      ],
      "parameters": {
        "color": 5,
        "width": 1140,
        "height": 796,
        "content": "## 3. Format, Save & Notify"
      },
      "typeVersion": 1
    },
    {
      "id": "3d9a733f-dbf2-49d3-a367-bb38d1f7e443",
      "name": "Receive WhatsApp Messages",
      "type": "n8n-nodes-base.webhook",
      "position": [
        1088,
        352
      ],
      "parameters": {
        "path": "travel-journal",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "5f5f9590-776a-42e5-8214-b3a9c1cb4990",
      "name": "JS #1: Validate & Aggregate Messages",
      "type": "n8n-nodes-base.code",
      "position": [
        1312,
        352
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Extract WhatsApp webhook payload\nconst body = $input.item.json.body || $input.item.json;\n\n// WhatsApp Business API typically sends messages in this format\nconst entry = body.entry?.[0] || {};\nconst changes = entry.changes?.[0] || {};\nconst value = changes.value || {};\nconst messages = value.messages || [body]; // Fallback for direct message format\n\n// Process all messages in the batch\nconst processedMessages = [];\nconst mediaItems = [];\nconst locations = [];\n\nfor (const msg of messages) {\n  const messageData = {\n    messageId: msg.id || `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n    from: msg.from || body.from || 'UNKNOWN_USER',\n    timestamp: msg.timestamp || body.timestamp || Date.now(),\n    type: msg.type || body.type || 'text'\n  };\n\n  // Parse message content based on type\n  if (msg.type === 'text' || body.text) {\n    messageData.text = msg.text?.body || body.text || '';\n    processedMessages.push(messageData);\n  } \n  else if (msg.type === 'image' || body.image) {\n    const image = msg.image || body.image || {};\n    mediaItems.push({\n      ...messageData,\n      mediaType: 'image',\n      mediaId: image.id,\n      caption: image.caption || '',\n      mimeType: image.mime_type || 'image/jpeg',\n      sha256: image.sha256 || null\n    });\n  }\n  else if (msg.type === 'location' || body.location) {\n    const loc = msg.location || body.location || {};\n    locations.push({\n      ...messageData,\n      latitude: loc.latitude,\n      longitude: loc.longitude,\n      name: loc.name || 'Unknown Location',\n      address: loc.address || ''\n    });\n  }\n  else if (msg.type === 'video' || body.video) {\n    const video = msg.video || body.video || {};\n    mediaItems.push({\n      ...messageData,\n      mediaType: 'video',\n      mediaId: video.id,\n      caption: video.caption || '',\n      mimeType: video.mime_type || 'video/mp4'\n    });\n  }\n}\n\n// If this is a simple single message format (not WhatsApp API format)\nif (processedMessages.length === 0 && mediaItems.length === 0 && body.message) {\n  processedMessages.push({\n    messageId: `msg-${Date.now()}`,\n    from: body.phone || body.userId || 'USER',\n    timestamp: Date.now(),\n    type: 'text',\n    text: body.message\n  });\n}\n\n// Add any photos from direct uploads\nif (body.photos && Array.isArray(body.photos)) {\n  body.photos.forEach((photo, idx) => {\n    mediaItems.push({\n      messageId: `photo-${Date.now()}-${idx}`,\n      from: body.phone || body.userId || 'USER',\n      timestamp: Date.now(),\n      type: 'image',\n      mediaType: 'image',\n      mediaUrl: photo.url || photo,\n      caption: photo.caption || ''\n    });\n  });\n}\n\n// Add location if provided directly\nif (body.location) {\n  locations.push({\n    messageId: `loc-${Date.now()}`,\n    from: body.phone || body.userId || 'USER',\n    timestamp: Date.now(),\n    type: 'location',\n    latitude: body.location.lat || body.location.latitude,\n    longitude: body.location.lng || body.location.longitude,\n    name: body.location.name || 'Current Location',\n    address: body.location.address || ''\n  });\n}\n\n// Determine travel day (group by date in user's timezone)\nconst firstTimestamp = processedMessages[0]?.timestamp || mediaItems[0]?.timestamp || Date.now();\nconst messageDate = new Date(parseInt(firstTimestamp) * 1000 || firstTimestamp);\nconst travelDay = messageDate.toISOString().split('T')[0]; // YYYY-MM-DD\n\n// Extract user identifier\nconst userId = body.userId || processedMessages[0]?.from || 'DEFAULT_USER';\nconst tripId = body.tripId || `TRIP-${travelDay}`;\n\n// Build aggregated journal entry\nconst journalEntry = {\n  // Identifiers\n  entryId: `ENTRY-${Date.now()}-${Math.random().toString(36).substr(2, 9).toUpperCase()}`,\n  userId: userId,\n  tripId: tripId,\n  travelDay: travelDay,\n  \n  // Content\n  messages: processedMessages.sort((a, b) => a.timestamp - b.timestamp),\n  media: mediaItems.sort((a, b) => a.timestamp - b.timestamp),\n  locations: locations,\n  \n  // Metadata\n  totalMessages: processedMessages.length,\n  totalMedia: mediaItems.length,\n  totalLocations: locations.length,\n  dayStartTime: messageDate.toISOString(),\n  \n  // Context\n  timezone: body.timezone || 'UTC',\n  destination: body.destination || locations[0]?.name || 'Unknown',\n  tripName: body.tripName || `Journey to ${locations[0]?.name || 'Unknown'}`,\n  \n  // Processing\n  receivedAt: new Date().toISOString(),\n  status: 'VALIDATED'\n};\n\n// Validation\nif (journalEntry.totalMessages === 0 && journalEntry.totalMedia === 0) {\n  throw new Error('No valid messages or media found in WhatsApp payload');\n}\n\nreturn { json: { journalEntry } };"
      },
      "typeVersion": 2
    },
    {
      "id": "5fce61fc-9b04-4c9d-b83f-18d81f861f6e",
      "name": "Fetch Previous Journal Entries",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        1568,
        352
      ],
      "parameters": {
        "operation": "search"
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3,
      "continueOnFail": true
    },
    {
      "id": "3fbf7371-d632-47e0-b116-17c4b257fb18",
      "name": "JS #2: Prepare AI Context",
      "type": "n8n-nodes-base.code",
      "position": [
        1760,
        352
      ],
      "parameters": {
        "jsCode": "// Get journal entry from validation step\nconst journalEntry = $('JS #1: Validate & Aggregate Messages').item.json.journalEntry;\n\n// Get previous journal data (if exists)\nconst previousJournals = $('Fetch Previous Journal Entries').all().map(i => i.json);\nconst hasPreviousEntries = previousJournals.length > 0;\n\n// Build chronological timeline of the day\nconst timeline = [];\n\n// Add all messages\njournalEntry.messages.forEach(msg => {\n  const time = new Date(parseInt(msg.timestamp) * 1000 || msg.timestamp);\n  timeline.push({\n    time: time.toISOString(),\n    timeFormatted: time.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }),\n    type: 'message',\n    content: msg.text\n  });\n});\n\n// Add all media with captions\njournalEntry.media.forEach(media => {\n  const time = new Date(parseInt(media.timestamp) * 1000 || media.timestamp);\n  timeline.push({\n    time: time.toISOString(),\n    timeFormatted: time.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }),\n    type: media.mediaType,\n    content: media.caption || `[${media.mediaType} - ${media.mediaId || 'uploaded'}]`,\n    mediaId: media.mediaId,\n    mediaUrl: media.mediaUrl || null\n  });\n});\n\n// Add all locations\njournalEntry.locations.forEach(loc => {\n  const time = new Date(parseInt(loc.timestamp) * 1000 || loc.timestamp);\n  timeline.push({\n    time: time.toISOString(),\n    timeFormatted: time.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }),\n    type: 'location',\n    content: `\ud83d\udccd ${loc.name}${loc.address ? ' - ' + loc.address : ''}`,\n    coordinates: `${loc.latitude}, ${loc.longitude}`\n  });\n});\n\n// Sort timeline chronologically\ntimeline.sort((a, b) => new Date(a.time) - new Date(b.time));\n\n// Build formatted timeline text for AI\nconst timelineText = timeline.map(item => \n  `${item.timeFormatted} - ${item.content}`\n).join('\\n');\n\n// Extract locations for context\nconst visitedLocations = journalEntry.locations.map(l => l.name).filter(Boolean);\nconst primaryLocation = visitedLocations[0] || journalEntry.destination;\n\n// Count media for AI awareness\nconst photoCount = journalEntry.media.filter(m => m.mediaType === 'image').length;\nconst videoCount = journalEntry.media.filter(m => m.mediaType === 'video').length;\n\n// Build context object for AI\nconst aiContext = {\n  journalEntry,\n  timeline,\n  timelineText,\n  primaryLocation,\n  visitedLocations,\n  photoCount,\n  videoCount,\n  hasPreviousEntries,\n  previousJournalCount: previousJournals.length,\n  previousJournalLink: previousJournals[0]?.webViewLink || null,\n  \n  // Metadata for AI\n  travelDayFormatted: new Date(journalEntry.travelDay).toLocaleDateString('en-US', {\n    weekday: 'long',\n    year: 'numeric',\n    month: 'long',\n    day: 'numeric'\n  }),\n  \n  // Instructions for AI\n  storyPrompt: {\n    tripName: journalEntry.tripName,\n    dayNumber: hasPreviousEntries ? previousJournals.length + 1 : 1,\n    destination: primaryLocation,\n    messageCount: journalEntry.totalMessages,\n    hasPhotos: photoCount > 0,\n    hasVideos: videoCount > 0,\n    hasLocations: journalEntry.totalLocations > 0\n  }\n};\n\nreturn [{ json: aiContext }];"
      },
      "typeVersion": 2
    },
    {
      "id": "4c81e688-874c-4d19-abcb-c8a479c0e6ab",
      "name": "Claude AI Story Generator",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1984,
        352
      ],
      "parameters": {
        "text": "=You are a creative travel writer who transforms everyday travel messages into engaging, narrative-style journal entries. Your writing is vivid, personal, and captures both experiences and emotions.\n\nTransform these WhatsApp messages from a day of travel into a beautifully written travel journal entry.\n\n**Trip Context:**\n- Trip Name: {{ $json.storyPrompt.tripName }}\n- Day Number: Day {{ $json.storyPrompt.dayNumber }}\n- Date: {{ $json.travelDayFormatted }}\n- Primary Destination: {{ $json.storyPrompt.destination }}\n- Has Previous Entries: {{ $json.hasPreviousEntries ? 'Yes' : 'No (this is the first day)' }}\n\n**Today's Activity Timeline:**\n```\n{{ $json.timelineText }}\n```\n\n**Content Available:**\n- Messages: {{ $json.storyPrompt.messageCount }}\n- Photos: {{ $json.photoCount }}\n- Videos: {{ $json.videoCount }}\n- Locations: {{ $json.visitedLocations.join(', ') || 'None recorded' }}\n\n**Writing Guidelines:**\n1. Create a cohesive narrative from the chronological messages\n2. Use vivid, sensory descriptions (sights, sounds, tastes, smells)\n3. Maintain first-person perspective and personal voice\n4. Integrate the timeline naturally \u2014 don't just list events\n5. Add emotional insights and reflections between factual descriptions\n6. Reference photos/videos where mentioned (e.g., \"As captured in the photo...\")\n7. Include specific locations and times when relevant to the story\n8. Create a story arc with a beginning, middle, and end for the day\n9. Write 300-600 words \u2014 detailed but not exhaustive\n10. End with a reflection or anticipation for tomorrow\n\n**Tone:** Personal, reflective, vivid, engaging \u2014 like a friend sharing their adventure\n\n**Response Format (JSON only, no markdown):**\n{\n  \"title\": \"Day X: [Engaging Title]\",\n  \"story\": \"[Full narrative text with paragraphs separated by \\\\n\\\\n]\",\n  \"highlights\": [\"Key moment 1\", \"Key moment 2\", \"Key moment 3\"],\n  \"locations\": [\"Location 1\", \"Location 2\"],\n  \"mood\": \"adventurous/relaxed/excited/contemplative/etc\",\n  \"tags\": [\"food\", \"culture\", \"nature\", \"etc\"],\n  \"photoMoments\": [\"Brief caption for each photo based on timeline\"],\n  \"tomorrowAnticipation\": \"Brief note about what's planned or hoped for next\",\n  \"wordCount\": 450\n}",
        "options": {
          "systemMessage": "You are a skilled travel writer. Respond with valid JSON only \u2014 no markdown, no code blocks, no preamble. Your narratives should be vivid, personal, and emotionally resonant."
        },
        "promptType": "define"
      },
      "typeVersion": 1.6
    },
    {
      "id": "1a9add1e-cb82-4b05-a795-104c5757c458",
      "name": "Claude Sonnet 4 Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        2056,
        576
      ],
      "parameters": {
        "model": "=claude-sonnet-4-20250514",
        "options": {
          "temperature": 0.7
        }
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "211af710-b124-4441-90a1-f962bf4b925a",
      "name": "Parse AI Story Response",
      "type": "n8n-nodes-base.code",
      "position": [
        2288,
        352
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const aiResponse = $input.item.json;\nlet aiText = aiResponse.response || aiResponse.output || aiResponse.text || '';\n\n// Handle Anthropic content array format\nif (aiResponse.content && Array.isArray(aiResponse.content)) {\n  aiText = aiResponse.content[0]?.text || '';\n}\n\n// Strip markdown code blocks\nconst cleanText = aiText\n  .replace(/```json\\s*/g, '')\n  .replace(/```\\s*/g, '')\n  .trim();\n\nlet story;\ntry {\n  story = JSON.parse(cleanText);\n} catch (error) {\n  throw new Error(`Failed to parse Claude story response: ${error.message}. Raw: ${cleanText.substring(0, 200)}`);\n}\n\n// Get context from previous steps\nconst aiContext = $('JS #2: Prepare AI Context').item.json;\nconst journalEntry = aiContext.journalEntry;\n\n// Build complete journal record\nconst journalRecord = {\n  // Story content\n  title: story.title,\n  story: story.story,\n  highlights: story.highlights || [],\n  locations: story.locations || aiContext.visitedLocations,\n  mood: story.mood || 'reflective',\n  tags: story.tags || [],\n  photoMoments: story.photoMoments || [],\n  tomorrowAnticipation: story.tomorrowAnticipation || '',\n  wordCount: story.wordCount || 0,\n  \n  // Original data\n  entryId: journalEntry.entryId,\n  tripId: journalEntry.tripId,\n  travelDay: journalEntry.travelDay,\n  dayNumber: aiContext.storyPrompt.dayNumber,\n  \n  // Metadata\n  messageCount: journalEntry.totalMessages,\n  photoCount: aiContext.photoCount,\n  videoCount: aiContext.videoCount,\n  locationCount: journalEntry.totalLocations,\n  \n  // Timeline\n  timeline: aiContext.timeline,\n  \n  // Processing info\n  generatedAt: new Date().toISOString(),\n  aiModel: 'claude-sonnet-4-20250514',\n  status: 'GENERATED'\n};\n\nreturn { json: { journalRecord, rawStory: story } };"
      },
      "typeVersion": 2
    },
    {
      "id": "e7eac15d-657f-4b97-bb41-c74578ab0d72",
      "name": "JS #3: Format Document",
      "type": "n8n-nodes-base.code",
      "position": [
        2560,
        352
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const record = $input.item.json.journalRecord;\n\n// Format story with proper paragraphs and spacing\nconst formattedStory = record.story.replace(/\\\\n\\\\n/g, '\\n\\n').trim();\n\n// Build complete document content\nconst documentContent = `${record.title}\n${'='.repeat(record.title.length)}\n\nDate: ${new Date(record.travelDay).toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}\nDay ${record.dayNumber} of ${record.tripId}\nMood: ${record.mood}\nLocations: ${record.locations.join(', ')}\n\n${'-'.repeat(80)}\n\n${formattedStory}\n\n${'-'.repeat(80)}\n\n\ud83d\udccc HIGHLIGHTS OF THE DAY\n${record.highlights.map((h, i) => `${i + 1}. ${h}`).join('\\n')}\n\n\ud83d\udcf8 PHOTO MOMENTS (${record.photoCount} photos)\n${record.photoMoments.map((p, i) => `${i + 1}. ${p}`).join('\\n')}\n\n\ud83d\udccd LOCATIONS VISITED\n${record.locations.map((l, i) => `${i + 1}. ${l}`).join('\\n')}\n\n\ud83c\udff7\ufe0f TAGS\n${record.tags.join(', ')}\n\n\ud83d\udcad TOMORROW\n${record.tomorrowAnticipation || 'To be continued...'}\n\n${'-'.repeat(80)}\n\nJournal Entry ID: ${record.entryId}\nGenerated: ${new Date(record.generatedAt).toLocaleString()}\nAI Model: ${record.aiModel}\nWord Count: ${record.wordCount} words\nMessages Processed: ${record.messageCount}\nMedia Included: ${record.photoCount} photos, ${record.videoCount} videos\nLocations Tracked: ${record.locationCount}\n`;\n\n// Prepare for Google Drive\nconst fileName = `${record.tripId}_Day_${record.dayNumber}_${record.travelDay}.txt`;\nconst fileTitle = `${record.title} - ${record.travelDay}`;\n\nreturn {\n  json: {\n    journalRecord: record,\n    documentContent,\n    fileName,\n    fileTitle,\n    \n    // Preview for notification\n    preview: `${record.title}\\n\\n${formattedStory.substring(0, 200)}...`,\n    \n    // Stats summary\n    summary: {\n      title: record.title,\n      dayNumber: record.dayNumber,\n      date: record.travelDay,\n      wordCount: record.wordCount,\n      highlights: record.highlights.length,\n      locations: record.locations.length,\n      photos: record.photoCount\n    }\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "0723988f-508f-41fa-a632-c97a6bcc66d7",
      "name": "Wait for Processing",
      "type": "n8n-nodes-base.wait",
      "position": [
        2784,
        352
      ],
      "parameters": {
        "amount": 2
      },
      "typeVersion": 1.1
    },
    {
      "id": "2974d479-377b-4402-9512-a80cb8762b4f",
      "name": "Create/Update Google Doc",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        3008,
        160
      ],
      "parameters": {
        "name": "={{ $json.fileName }}",
        "driveId": {
          "__rl": true,
          "mode": "id",
          "value": "=bghy65432f"
        },
        "options": {
          "keepRevisionForever": true
        },
        "folderId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_TRAVEL_JOURNAL_FOLDER_ID"
        }
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3
    },
    {
      "id": "5cdce004-9f6c-42b7-a99f-fd43f6651b17",
      "name": "Send WhatsApp Confirmation",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3008,
        352
      ],
      "parameters": {
        "url": "https://graph.facebook.com/v18.0/YOUR_PHONE_NUMBER_ID/messages",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "sendHeaders": true,
        "authentication": "predefinedCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "messaging_product",
              "value": "whatsapp"
            },
            {
              "name": "to",
              "value": "={{ $('JS #1: Validate & Aggregate Messages').item.json.journalEntry.userId }}"
            },
            {
              "name": "type",
              "value": "text"
            },
            {
              "name": "text",
              "value": "={{ JSON.stringify({ body: '\u2728 Your travel journal entry is ready!\\n\\n' + $json.summary.title + '\\n\\nWord count: ' + $json.summary.wordCount + ' words\\nHighlights: ' + $json.summary.highlights + '\\nLocations: ' + $json.summary.locations.length + '\\n\\nView in Drive: [Link will be in next update]' }) }}"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "nodeCredentialType": "whatsAppApi"
      },
      "credentials": {
        "whatsAppApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "9eeb0e6c-4d15-444e-86c9-80cba3975ea1",
      "name": "Send Email with Journal Link",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        3008,
        544
      ],
      "parameters": {
        "options": {},
        "subject": "=\u2708\ufe0f {{ $json.summary.title }} - Your Travel Journal is Ready!",
        "toEmail": "user@example.com",
        "fromEmail": "user@example.com"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1,
      "continueOnFail": true
    },
    {
      "id": "5ae1b9f1-fac1-4e0d-8f89-299c0fe8a95d",
      "name": "Build Success Response",
      "type": "n8n-nodes-base.code",
      "position": [
        3232,
        352
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const formatted = $('JS #3: Format Document').item.json;\nconst driveDoc = $('Create/Update Google Doc').item.json;\n\nreturn {\n  json: {\n    success: true,\n    message: 'Travel journal entry generated and saved successfully!',\n    journalEntry: {\n      id: formatted.journalRecord.entryId,\n      title: formatted.summary.title,\n      dayNumber: formatted.summary.dayNumber,\n      date: formatted.summary.date,\n      wordCount: formatted.summary.wordCount\n    },\n    document: {\n      name: formatted.fileName,\n      id: driveDoc.id || 'processing',\n      link: driveDoc.webViewLink || 'will be available shortly'\n    },\n    stats: formatted.summary,\n    processedAt: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "01ea1025-7af2-42a1-87b9-54171740e327",
      "name": "Send Response to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        3456,
        352
      ],
      "parameters": {
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        },
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json, null, 2) }}"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "849c44c3-5cd2-445c-bc69-8f0444bdc186",
  "connections": {
    "Wait for Processing": {
      "main": [
        [
          {
            "node": "Create/Update Google Doc",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send WhatsApp Confirmation",
            "type": "main",
            "index": 0
          },
          {
            "node": "Send Email with Journal Link",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Sonnet 4 Model": {
      "ai_languageModel": [
        [
          {
            "node": "Claude AI Story Generator",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Build Success Response": {
      "main": [
        [
          {
            "node": "Send Response to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JS #3: Format Document": {
      "main": [
        [
          {
            "node": "Wait for Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse AI Story Response": {
      "main": [
        [
          {
            "node": "JS #3: Format Document",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create/Update Google Doc": {
      "main": [
        [
          {
            "node": "Build Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude AI Story Generator": {
      "main": [
        [
          {
            "node": "Parse AI Story Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JS #2: Prepare AI Context": {
      "main": [
        [
          {
            "node": "Claude AI Story Generator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive WhatsApp Messages": {
      "main": [
        [
          {
            "node": "JS #1: Validate & Aggregate Messages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send WhatsApp Confirmation": {
      "main": [
        [
          {
            "node": "Build Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Email with Journal Link": {
      "main": [
        [
          {
            "node": "Build Success Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Previous Journal Entries": {
      "main": [
        [
          {
            "node": "JS #2: Prepare AI Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "JS #1: Validate & Aggregate Messages": {
      "main": [
        [
          {
            "node": "Fetch Previous Journal Entries",
            "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

Automatically converts your daily WhatsApp messages and photos from travels into beautifully structured travel stories, saved as documents in Google Drive. Receive WhatsApp Updates - Webhook captures messages, photos, and locations from your travel day Validate & Aggregate…

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

Automatically transforms your travel photos and notes into beautiful journals, highlight reels, and review drafts using Claude's vision and language capabilities. Trip Completion Trigger - Webhook or

HTTP Request, Google Sheets, Agent +3
AI & RAG

This workflow automates pre-dispatch customs document validation for international shipments. It ingests shipping document packages, extracts content from each file, uses Claude AI to cross-validate a

Google Drive Trigger, Google Drive, Agent +4
AI & RAG

This workflow ingests property document packages submitted via webhook or monitored cloud storage, extracts text from each file, runs Claude AI to verify legal compliance, detect missing or expired do

Google Drive Trigger, Google Drive, Agent +4
AI & RAG

What if AI didn't just write content—but actually thought about how to write it? This n8n workflow revolutionizes content creation by deploying multiple specialized AI agents that handle every aspect

Tool Http Request, Anthropic Chat, Airtable +7
AI & RAG

Tired of grinding out YouTube content? This n8n workflow turns AI into your personal video factory—creating engaging, faceless shorts on autopilot. Perfect for creators, marketers, or side-hustlers lo

HTTP Request, Google Drive, Google Sheets +6