AutomationFlowsAI & RAG › Create Post‑trip Journals and Review Drafts with Claude Sonnet Vision

Create Post‑trip Journals and Review Drafts with Claude Sonnet Vision

ByOneclick AI Squad @oneclick-ai on n8n.io

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 scheduled check for recently ended trips Validate Trip Data - Confirms trip…

Webhook trigger★★★★★ complexityAI-powered32 nodesHTTP RequestGoogle SheetsAgentAnthropic ChatGoogle DriveEmail Send
AI & RAG Trigger: Webhook Nodes: 32 Complexity: ★★★★★ AI nodes: yes Added:

This workflow corresponds to n8n.io template #14912 — 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": "cuAUl1F40meDnBdT",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Post-Trip Memory & Review Builder with Claude AI Vision",
  "tags": [],
  "nodes": [
    {
      "id": "3c4ce5be-bed0-4c83-b0d0-4d2d8060b2ef",
      "name": "Main Documentation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -560,
        304
      ],
      "parameters": {
        "width": 1072,
        "height": 1184,
        "content": "## Post-Trip Memory & Review Builder with Claude AI\n\nAutomatically transforms your travel photos and notes into beautiful journals, highlight reels, and review drafts using Claude's vision and language capabilities.\n\n### How it works\n\n1. **Trip Completion Trigger** - Webhook or scheduled check for recently ended trips\n2. **Validate Trip Data** - Confirms trip details, dates, and destination information\n3. **Fetch Photos from Google Photos** - Retrieves all photos from the trip date range\n4. **Extract Photo Metadata** - Gets EXIF data (location, timestamps, camera settings)\n5. **Fetch Trip Notes** - Pulls journal entries, receipts, bookings from Google Drive/Sheets\n6. **AI Photo Analysis** - Claude Vision analyzes each photo for content, mood, significance\n7. **Generate Chronological Story** - Creates narrative flow from photo sequence\n8. **Create Travel Journal** - Formats beautiful journal document with photos and stories\n9. **Draft Platform Reviews** - Generates review text for TripAdvisor, Google, social media\n10. **Create Highlights Reel Script** - Suggests best photos and captions for video/slideshow\n11. **Store Outputs** - Saves to Google Drive with organized folder structure\n12. **Send Delivery Package** - Emails journal PDF, review drafts, and highlights to user\n13. **Batch Processing** - Daily check for trips ended in last 7 days\n14. **Analytics & Insights** - Tracks most memorable moments, photo quality scores\n\n### Setup Steps\n\n1. Import workflow into n8n\n2. Configure credentials:\n   - **Anthropic API** - Claude AI with vision for photo analysis\n   - **Google Photos API** - Photo retrieval and metadata\n   - **Google Drive API** - Trip notes and output storage\n   - **Google Sheets** - Trip tracking and metadata\n   - **SMTP / Gmail** - Delivery notifications\n   - **OpenWeatherMap** (optional) - Historical weather data\n3. Create Google Sheets with tabs:\n   - `trips` - Trip records with start/end dates\n   - `trip_notes` - Daily journal entries\n   - `generated_content` - AI-generated journals and reviews\n   - `analytics` - Photo quality and memory scores\n4. Set up Google Drive folder structure:\n   - `/Travel Memories/{Year}/{Trip Name}/Photos`\n   - `/Travel Memories/{Year}/{Trip Name}/Journal`\n   - `/Travel Memories/{Year}/{Trip Name}/Reviews`\n5. Configure API keys and resource IDs\n6. Activate both webhook and scheduled workflows\n\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "74dcd7e8-f4c6-4be1-acd1-ec2d1130fa54",
      "name": "Section 1: Trip Intake",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        736,
        448
      ],
      "parameters": {
        "color": 4,
        "width": 480,
        "height": 400,
        "content": "## 1. Trip Intake & Validation"
      },
      "typeVersion": 1
    },
    {
      "id": "1191cc7a-fdae-40a5-ba88-a306577285a4",
      "name": "Section 2: Data Collection",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1264,
        288
      ],
      "parameters": {
        "color": 4,
        "width": 960,
        "height": 800,
        "content": "## 2. Photo & Notes Collection"
      },
      "typeVersion": 1
    },
    {
      "id": "c67bdf6b-ae9b-4636-830d-0fada038fb10",
      "name": "Section 3: AI Analysis",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2272,
        272
      ],
      "parameters": {
        "color": 4,
        "width": 1120,
        "height": 880,
        "content": "## 3. AI Photo Analysis & Content Generation"
      },
      "typeVersion": 1
    },
    {
      "id": "8c32abd5-d862-4a2d-bea1-b3b8dc1f6d00",
      "name": "Section 4: Output Creation",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3472,
        240
      ],
      "parameters": {
        "color": 4,
        "width": 1280,
        "height": 960,
        "content": "## 4. Journal, Reviews & Delivery"
      },
      "typeVersion": 1
    },
    {
      "id": "7d183282-bf52-4028-98bc-e4f532434cda",
      "name": "Section 5: Batch Processing",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        816,
        1296
      ],
      "parameters": {
        "color": 4,
        "width": 1520,
        "height": 420,
        "content": "## 5. Daily Batch Processing & Analytics"
      },
      "typeVersion": 1
    },
    {
      "id": "959353d7-972d-4751-96a8-d11a95e9bd4e",
      "name": "Trip Completion Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        816,
        608
      ],
      "parameters": {
        "path": "trip-completed",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2
    },
    {
      "id": "1331b75f-f950-46ef-a880-47f1ed5d79e9",
      "name": "Validate Trip Data",
      "type": "n8n-nodes-base.code",
      "position": [
        1040,
        608
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Extract trip data\nconst body = $input.item.json.body || $input.item.json;\n\n// Required field validation\nconst requiredFields = ['tripId', 'userId', 'userName', 'userEmail', 'tripDetails'];\nconst missing = requiredFields.filter(f => !body[f]);\nif (missing.length > 0) {\n  throw new Error(`Missing required fields: ${missing.join(', ')}`);\n}\n\n// Validate trip details\nconst tripDetails = body.tripDetails || {};\nif (!tripDetails.destination || !tripDetails.startDate || !tripDetails.endDate) {\n  throw new Error('Trip must have destination, startDate, and endDate');\n}\n\n// Validate dates\nconst startDate = new Date(tripDetails.startDate);\nconst endDate = new Date(tripDetails.endDate);\nconst today = new Date();\n\nif (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {\n  throw new Error('Invalid date format');\n}\n\nif (endDate < startDate) {\n  throw new Error('End date must be after start date');\n}\n\nif (endDate > today) {\n  throw new Error('Trip has not ended yet');\n}\n\n// Calculate trip duration\nconst durationDays = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24));\n\nif (durationDays > 365) {\n  throw new Error('Trip duration exceeds maximum of 365 days');\n}\n\n// Normalize photo sources\nconst photoSources = body.photoSources || {};\nconst normalizedPhotoSources = {\n  googlePhotosAlbumId: photoSources.googlePhotosAlbumId || null,\n  includeSharedPhotos: photoSources.includeSharedPhotos !== false,\n  dateRange: {\n    start: tripDetails.startDate,\n    end: tripDetails.endDate\n  }\n};\n\n// Normalize output preferences\nconst outputPrefs = body.outputPreferences || {};\nconst normalizedOutputPrefs = {\n  journalStyle: outputPrefs.journalStyle || 'narrative',\n  includeMapRoute: outputPrefs.includeMapRoute !== false,\n  reviewPlatforms: outputPrefs.reviewPlatforms || ['tripadvisor', 'google'],\n  highlightCount: Math.min(outputPrefs.highlightCount || 10, 20),\n  privacyLevel: outputPrefs.privacyLevel || 'private'\n};\n\n// Build validated trip record\nconst validatedTrip = {\n  // Identifiers\n  tripId: body.tripId.trim(),\n  userId: body.userId.trim(),\n  userName: body.userName.trim(),\n  userEmail: body.userEmail.trim().toLowerCase(),\n  \n  // Trip details\n  destination: tripDetails.destination.trim(),\n  startDate: tripDetails.startDate,\n  endDate: tripDetails.endDate,\n  durationDays,\n  tripType: tripDetails.tripType || 'general',\n  mainInterests: tripDetails.mainInterests || [],\n  \n  // Data sources\n  photoSources: normalizedPhotoSources,\n  notesLocation: body.notesLocation || {},\n  \n  // Output preferences\n  outputPreferences: normalizedOutputPrefs,\n  deliveryOptions: body.deliveryOptions || { emailPDF: true, saveToDrive: true },\n  \n  // Metadata\n  processedAt: new Date().toISOString(),\n  processingId: `PROC-${Date.now()}-${Math.random().toString(36).substr(2, 9).toUpperCase()}`,\n  status: 'VALIDATED',\n  daysAfterTripEnd: Math.ceil((today - endDate) / (1000 * 60 * 60 * 24))\n};\n\nreturn { json: { validatedTrip } };"
      },
      "typeVersion": 2
    },
    {
      "id": "eb654ecd-6cdf-44bc-a0d0-f0db6f29d3e7",
      "name": "Fetch Google Photos",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1344,
        416
      ],
      "parameters": {
        "url": "https://photoslibrary.googleapis.com/v1/mediaItems:search",
        "method": "POST",
        "options": {
          "timeout": 30000
        },
        "sendBody": true,
        "authentication": "predefinedCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "albumId",
              "value": "={{ $json.validatedTrip.photoSources.googlePhotosAlbumId }}"
            },
            {
              "name": "pageSize",
              "value": "100"
            },
            {
              "name": "filters",
              "value": "={{ JSON.stringify({ dateFilter: { ranges: [{ startDate: { year: new Date($json.validatedTrip.startDate).getFullYear(), month: new Date($json.validatedTrip.startDate).getMonth() + 1, day: new Date($json.validatedTrip.startDate).getDate() }, endDate: { year: new Date($json.validatedTrip.endDate).getFullYear(), month: new Date($json.validatedTrip.endDate).getMonth() + 1, day: new Date($json.validatedTrip.endDate).getDate() } }] } }) }}"
            }
          ]
        },
        "nodeCredentialType": "googlePhotosOAuth2Api"
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "edc89b75-695e-4ecb-bb7d-6a1610429bcb",
      "name": "Fetch Trip Notes",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1344,
        624
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "trip_notes"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5,
      "continueOnFail": true
    },
    {
      "id": "dd5ebeab-711c-4460-9b74-7248318af238",
      "name": "Extract Photo Metadata",
      "type": "n8n-nodes-base.code",
      "position": [
        1568,
        416
      ],
      "parameters": {
        "jsCode": "const validatedTrip = $('Validate Trip Data').item.json.validatedTrip;\nconst photosResponse = $input.item.json;\n\n// Extract media items\nconst mediaItems = photosResponse.mediaItems || [];\n\nif (mediaItems.length === 0) {\n  return [{ json: { \n    validatedTrip,\n    photos: [],\n    photoCount: 0,\n    error: 'No photos found for this trip'\n  }}];\n}\n\n// Process each photo\nconst photos = mediaItems.map((photo, index) => {\n  const metadata = photo.mediaMetadata || {};\n  const creationTime = new Date(metadata.creationTime || photo.creationTime);\n  \n  return {\n    photoId: photo.id,\n    filename: photo.filename,\n    mimeType: photo.mimeType,\n    baseUrl: photo.baseUrl,\n    productUrl: photo.productUrl,\n    \n    // Metadata\n    creationTime: creationTime.toISOString(),\n    width: metadata.width,\n    height: metadata.height,\n    \n    // Location data\n    location: metadata.location || null,\n    \n    // Camera info\n    cameraInfo: {\n      cameraMake: metadata.photo?.cameraMake || 'Unknown',\n      cameraModel: metadata.photo?.cameraModel || 'Unknown',\n      focalLength: metadata.photo?.focalLength || null,\n      apertureFNumber: metadata.photo?.apertureFNumber || null,\n      isoEquivalent: metadata.photo?.isoEquivalent || null\n    },\n    \n    // Processing metadata\n    photoIndex: index,\n    downloadUrl: `${photo.baseUrl}=w2048-h2048`,\n    thumbnailUrl: `${photo.baseUrl}=w400-h400`\n  };\n});\n\n// Sort photos chronologically\nphotos.sort((a, b) => new Date(a.creationTime) - new Date(b.creationTime));\n\n// Calculate statistics\nconst photosWithLocation = photos.filter(p => p.location).length;\nconst uniqueDays = new Set(photos.map(p => p.creationTime.split('T')[0])).size;\n\nreturn [{ json: {\n  validatedTrip,\n  photos,\n  photoStats: {\n    totalPhotos: photos.length,\n    photosWithLocation,\n    locationCoverage: Math.round((photosWithLocation / photos.length) * 100),\n    uniqueDays,\n    avgPhotosPerDay: Math.round(photos.length / uniqueDays),\n    dateRange: {\n      first: photos[0]?.creationTime,\n      last: photos[photos.length - 1]?.creationTime\n    }\n  }\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "d30dab4a-6d76-4e41-bee7-7db0543b71cd",
      "name": "Process Trip Notes",
      "type": "n8n-nodes-base.code",
      "position": [
        1568,
        624
      ],
      "parameters": {
        "jsCode": "const validatedTrip = $('Validate Trip Data').item.json.validatedTrip;\nconst notesData = $input.all().map(i => i.json);\n\n// Filter notes for this specific trip\nconst tripNotes = notesData.filter(note => \n  note.tripId === validatedTrip.tripId ||\n  (note.userId === validatedTrip.userId && \n   new Date(note.date) >= new Date(validatedTrip.startDate) &&\n   new Date(note.date) <= new Date(validatedTrip.endDate))\n);\n\n// Sort notes by date\ntripNotes.sort((a, b) => new Date(a.date) - new Date(b.date));\n\n// Organize notes by day\nconst notesByDay = {};\ntripNotes.forEach(note => {\n  const dateKey = note.date.split('T')[0];\n  if (!notesByDay[dateKey]) {\n    notesByDay[dateKey] = [];\n  }\n  notesByDay[dateKey].push({\n    time: note.time || 'all-day',\n    content: note.content || note.note || '',\n    location: note.location || null,\n    tags: note.tags ? note.tags.split(',').map(t => t.trim()) : [],\n    mood: note.mood || 'neutral',\n    highlights: note.highlights || false\n  });\n});\n\n// Extract key information\nconst allTags = [...new Set(tripNotes.flatMap(n => \n  n.tags ? n.tags.split(',').map(t => t.trim()) : []\n))];\n\nconst mentionedPlaces = [...new Set(tripNotes\n  .filter(n => n.location)\n  .map(n => n.location)\n)];\n\nconst highlightNotes = tripNotes.filter(n => n.highlights);\n\nreturn [{ json: {\n  validatedTrip,\n  tripNotes: {\n    byDay: notesByDay,\n    all: tripNotes,\n    stats: {\n      totalNotes: tripNotes.length,\n      daysWithNotes: Object.keys(notesByDay).length,\n      tags: allTags,\n      mentionedPlaces,\n      highlightCount: highlightNotes.length\n    }\n  }\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "c68843f3-26e5-4f4e-a5f9-11452e539398",
      "name": "Merge Photos & Notes",
      "type": "n8n-nodes-base.code",
      "position": [
        1744,
        544
      ],
      "parameters": {
        "jsCode": "const photoData = $('Extract Photo Metadata').item.json;\nconst notesData = $('Process Trip Notes').item.json;\n\nconst validatedTrip = photoData.validatedTrip;\nconst photos = photoData.photos;\nconst photoStats = photoData.photoStats;\nconst tripNotes = notesData.tripNotes;\n\n// Organize photos by day\nconst photosByDay = {};\nphotos.forEach(photo => {\n  const dateKey = photo.creationTime.split('T')[0];\n  if (!photosByDay[dateKey]) {\n    photosByDay[dateKey] = [];\n  }\n  photosByDay[dateKey].push(photo);\n});\n\n// Create day-by-day timeline\nconst timeline = [];\nconst allDates = [...new Set([...Object.keys(photosByDay), ...Object.keys(tripNotes.byDay)])];\nallDates.sort();\n\nallDates.forEach(date => {\n  const dayPhotos = photosByDay[date] || [];\n  const dayNotes = tripNotes.byDay[date] || [];\n  \n  timeline.push({\n    date,\n    dayNumber: timeline.length + 1,\n    photos: dayPhotos,\n    photoCount: dayPhotos.length,\n    notes: dayNotes,\n    noteCount: dayNotes.length,\n    locations: [...new Set([\n      ...dayPhotos.filter(p => p.location).map(p => p.location),\n      ...dayNotes.filter(n => n.location).map(n => n.location)\n    ])]\n  });\n});\n\n// Prepare data for AI analysis\nconst aiAnalysisInput = {\n  validatedTrip,\n  timeline,\n  statistics: {\n    photos: photoStats,\n    notes: tripNotes.stats,\n    tripDuration: validatedTrip.durationDays,\n    totalContent: photos.length + tripNotes.all.length\n  },\n  rawPhotos: photos,\n  rawNotes: tripNotes.all\n};\n\nreturn [{ json: aiAnalysisInput }];"
      },
      "typeVersion": 2
    },
    {
      "id": "1feccfcd-6bf5-4782-b5a6-791d564e3c96",
      "name": "Analyze Photos with Claude Vision",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2368,
        416
      ],
      "parameters": {
        "text": "=You are an expert travel photographer and storyteller with the ability to analyze images and create compelling narratives.\n\nAnalyze this trip's photos and notes to create a comprehensive memory analysis.\n\n**Trip Information:**\n- Destination: {{ $json.validatedTrip.destination }}\n- Duration: {{ $json.validatedTrip.durationDays }} days ({{ $json.validatedTrip.startDate }} to {{ $json.validatedTrip.endDate }})\n- Trip Type: {{ $json.validatedTrip.tripType }}\n- Interests: {{ $json.validatedTrip.mainInterests.join(', ') }}\n\n**Content Overview:**\n- Total Photos: {{ $json.statistics.photos.totalPhotos }}\n- Photos with Location: {{ $json.statistics.photos.photosWithLocation }} ({{ $json.statistics.photos.locationCoverage }}%)\n- Days with Photos: {{ $json.statistics.photos.uniqueDays }}\n- Total Notes: {{ $json.statistics.notes.totalNotes }}\n- Days with Notes: {{ $json.statistics.notes.daysWithNotes }}\n\n**Timeline Summary:**\n{{ $json.timeline.map(day => `Day ${day.dayNumber} (${day.date}): ${day.photoCount} photos, ${day.noteCount} notes`).join('\\n') }}\n\n**Your Task:**\n1. Analyze the photo collection for themes, recurring subjects, and visual progression\n2. Identify the most significant moments based on photo quality, uniqueness, and emotional impact\n3. Detect the overall mood and travel style (adventure, relaxation, cultural exploration, etc.)\n4. Categorize photos into themes (food, architecture, nature, people, activities, etc.)\n5. Identify standout photos that would make great highlights\n6. Create a narrative arc for the trip story\n7. Note any missing elements or gaps in the photo coverage\n\n**Response Format (JSON only):**\n{\n  \"tripAnalysis\": {\n    \"overallMood\": \"adventurous/relaxing/cultural/mixed\",\n    \"travelStyle\": \"solo explorer/group adventure/romantic getaway/family trip\",\n    \"dominantThemes\": [\"theme1\", \"theme2\", \"theme3\"],\n    \"narrativeArc\": \"Brief description of how the trip unfolded\",\n    \"emotionalJourney\": \"Description of emotional progression through the trip\"\n  },\n  \"photoCategories\": {\n    \"food\": { \"count\": 0, \"quality\": \"high/medium/low\", \"highlights\": [] },\n    \"architecture\": { \"count\": 0, \"quality\": \"high/medium/low\", \"highlights\": [] },\n    \"nature\": { \"count\": 0, \"quality\": \"high/medium/low\", \"highlights\": [] },\n    \"people\": { \"count\": 0, \"quality\": \"high/medium/low\", \"highlights\": [] },\n    \"activities\": { \"count\": 0, \"quality\": \"high/medium/low\", \"highlights\": [] },\n    \"street\": { \"count\": 0, \"quality\": \"high/medium/low\", \"highlights\": [] },\n    \"nightlife\": { \"count\": 0, \"quality\": \"high/medium/low\", \"highlights\": [] }\n  },\n  \"highlightPhotos\": [\n    {\n      \"photoIndex\": 0,\n      \"category\": \"category name\",\n      \"qualityScore\": 95,\n      \"caption\": \"Engaging caption for this photo\",\n      \"significance\": \"Why this photo is special\",\n      \"suggestedTitle\": \"Memorable title\",\n      \"mood\": \"happy/serene/exciting/contemplative\",\n      \"bestUseCase\": \"cover/highlight/social_media/print\"\n    }\n  ],\n  \"dayByDayHighlights\": [\n    {\n      \"dayNumber\": 1,\n      \"date\": \"YYYY-MM-DD\",\n      \"summary\": \"What happened this day\",\n      \"bestMoment\": \"The standout moment\",\n      \"photoHighlights\": [0, 3, 7],\n      \"mood\": \"overall mood\"\n    }\n  ],\n  \"missingElements\": [\"sunset photos\", \"group photos\", \"etc\"],\n  \"qualityAssessment\": {\n    \"compositionScore\": 85,\n    \"varietyScore\": 90,\n    \"technicalScore\": 80,\n    \"storytellingScore\": 88,\n    \"overallScore\": 86\n  }\n}",
        "options": {
          "systemMessage": "You are an expert photo analyst and travel storyteller. Respond with valid JSON only \u2014 no markdown, no code blocks, no preamble."
        },
        "promptType": "define"
      },
      "typeVersion": 1.6
    },
    {
      "id": "e8d05dd3-08db-46a8-ab66-e49382c0117e",
      "name": "Claude Sonnet 4 with Vision",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        2288,
        528
      ],
      "parameters": {
        "model": "=claude-sonnet-4-20250514",
        "options": {
          "temperature": 0.4
        }
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2e8719bf-9679-45bb-b12e-37c2c1540f47",
      "name": "Generate Travel Journal Content",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2304,
        704
      ],
      "parameters": {
        "text": "=You are an expert travel writer creating a beautiful, engaging travel journal.\n\nUsing the trip analysis and photo insights, create compelling journal content.\n\n**Trip Details:**\n- Destination: {{ $('Merge Photos & Notes').item.json.validatedTrip.destination }}\n- Duration: {{ $('Merge Photos & Notes').item.json.validatedTrip.durationDays }} days\n- Style: {{ $('Merge Photos & Notes').item.json.validatedTrip.outputPreferences.journalStyle }}\n\n**Create Journal Sections:**\n1. Opening reflection - Set the scene and anticipation\n2. Day-by-day narrative - Engaging story for each day\n3. Highlight moments - Deep dive into best experiences\n4. Closing reflection - What the trip meant, lessons learned\n5. Travel tips - Practical insights for future travelers\n\n**Response Format (JSON):**\n{\n  \"journalTitle\": \"Compelling title for the journal\",\n  \"openingReflection\": \"2-3 paragraphs setting the scene\",\n  \"dailyNarratives\": [\n    {\n      \"dayNumber\": 1,\n      \"date\": \"YYYY-MM-DD\",\n      \"title\": \"Day title\",\n      \"narrative\": \"Engaging 3-4 paragraph story of the day\",\n      \"photoReferences\": [0, 2, 5],\n      \"keyMoment\": \"One sentence highlight\"\n    }\n  ],\n  \"highlightMoments\": [\n    {\n      \"title\": \"Moment title\",\n      \"story\": \"2-3 paragraphs telling this story in detail\",\n      \"photoReference\": 12,\n      \"emotion\": \"How it felt\"\n    }\n  ],\n  \"closingReflection\": \"2-3 paragraphs of reflection and meaning\",\n  \"travelTips\": [\n    \"Practical tip 1\",\n    \"Practical tip 2\",\n    \"Practical tip 3\"\n  ],\n  \"favoriteMemories\": [\n    \"Memory 1\",\n    \"Memory 2\",\n    \"Memory 3\"\n  ]\n}",
        "options": {
          "systemMessage": "You are a professional travel writer. Create engaging, vivid, and personal journal content. Respond with valid JSON only."
        },
        "promptType": "define"
      },
      "typeVersion": 1.6
    },
    {
      "id": "cbc99be2-f089-40d2-a8c9-c10525997440",
      "name": "Generate Platform Review Drafts",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2368,
        992
      ],
      "parameters": {
        "text": "=You are an expert at writing authentic, helpful travel reviews for different platforms.\n\nCreate review drafts optimized for each platform's style and audience.\n\n**Trip Details:**\n- Destination: {{ $('Merge Photos & Notes').item.json.validatedTrip.destination }}\n- Duration: {{ $('Merge Photos & Notes').item.json.validatedTrip.durationDays }} days\n- Trip Type: {{ $('Merge Photos & Notes').item.json.validatedTrip.tripType }}\n\n**Target Platforms:**\n{{ $('Merge Photos & Notes').item.json.validatedTrip.outputPreferences.reviewPlatforms.join(', ') }}\n\n**Guidelines:**\n- TripAdvisor: Detailed, structured, pros/cons, specific recommendations\n- Google Reviews: Concise, helpful, local tips, 150-300 words\n- Instagram: Visual storytelling, hashtags, engaging first line, 100-150 words\n- Blog: Long-form narrative, SEO-friendly, detailed itinerary\n\n**Response Format (JSON):**\n{\n  \"reviews\": [\n    {\n      \"platform\": \"tripadvisor\",\n      \"title\": \"Review title\",\n      \"rating\": 5,\n      \"text\": \"Full review text\",\n      \"pros\": [\"Pro 1\", \"Pro 2\"],\n      \"cons\": [\"Con 1\"] ,\n      \"recommendations\": [\"Tip 1\", \"Tip 2\"],\n      \"visitDate\": \"Month Year\",\n      \"travelType\": \"Solo/Couple/Family/Friends\"\n    },\n    {\n      \"platform\": \"google\",\n      \"rating\": 5,\n      \"text\": \"Concise review text\",\n      \"highlights\": [\"Highlight 1\", \"Highlight 2\"]\n    },\n    {\n      \"platform\": \"instagram\",\n      \"caption\": \"Engaging caption with story\",\n      \"hashtags\": [\"#travel\", \"#destination\"],\n      \"callToAction\": \"Question or engagement prompt\",\n      \"photoSuggestions\": [0, 3, 7, 12]\n    }\n  ],\n  \"blogPost\": {\n    \"title\": \"SEO-friendly blog title\",\n    \"metaDescription\": \"160-character meta description\",\n    \"introduction\": \"Hook paragraph\",\n    \"sections\": [\n      {\n        \"heading\": \"Section title\",\n        \"content\": \"Section content\"\n      }\n    ],\n    \"conclusion\": \"Closing paragraph\",\n    \"seoKeywords\": [\"keyword1\", \"keyword2\"]\n  }\n}",
        "options": {
          "systemMessage": "You are an expert travel reviewer. Create authentic, platform-appropriate review content. Respond with valid JSON only."
        },
        "promptType": "define"
      },
      "typeVersion": 1.6
    },
    {
      "id": "3c633f98-7693-4ab9-82a4-2855f5608c6a",
      "name": "Parse All AI Outputs",
      "type": "n8n-nodes-base.code",
      "position": [
        2672,
        720
      ],
      "parameters": {
        "jsCode": "// Helper function to parse AI response\nfunction parseAIResponse(response) {\n  let text = response.response || response.output || response.text || '';\n  \n  if (response.content && Array.isArray(response.content)) {\n    text = response.content[0]?.text || '';\n  }\n  \n  const cleanText = text\n    .replace(/```json\\s*/g, '')\n    .replace(/```\\s*/g, '')\n    .trim();\n  \n  return JSON.parse(cleanText);\n}\n\n// Get original merged data\nconst mergedData = $('Merge Photos & Notes').item.json;\n\n// Parse photo analysis\nconst photoAnalysisRaw = $('Analyze Photos with Claude Vision').item.json;\nconst photoAnalysis = parseAIResponse(photoAnalysisRaw);\n\n// Parse journal content\nconst journalRaw = $('Generate Travel Journal Content').item.json;\nconst journalContent = parseAIResponse(journalRaw);\n\n// Parse review drafts\nconst reviewsRaw = $('Generate Platform Review Drafts').item.json;\nconst reviewDrafts = parseAIResponse(reviewsRaw);\n\n// Build complete memory package\nconst memoryPackage = {\n  // Trip metadata\n  tripId: mergedData.validatedTrip.tripId,\n  userId: mergedData.validatedTrip.userId,\n  userName: mergedData.validatedTrip.userName,\n  userEmail: mergedData.validatedTrip.userEmail,\n  destination: mergedData.validatedTrip.destination,\n  startDate: mergedData.validatedTrip.startDate,\n  endDate: mergedData.validatedTrip.endDate,\n  durationDays: mergedData.validatedTrip.durationDays,\n  \n  // Generated content\n  photoAnalysis,\n  journal: journalContent,\n  reviews: reviewDrafts,\n  \n  // Original data references\n  photos: mergedData.rawPhotos,\n  notes: mergedData.rawNotes,\n  timeline: mergedData.timeline,\n  \n  // Statistics\n  statistics: {\n    ...mergedData.statistics,\n    highlightPhotosCount: photoAnalysis.highlightPhotos?.length || 0,\n    overallQualityScore: photoAnalysis.qualityAssessment?.overallScore || 0\n  },\n  \n  // Metadata\n  generatedAt: new Date().toISOString(),\n  processingId: mergedData.validatedTrip.processingId,\n  aiModel: 'claude-sonnet-4-20250514',\n  status: 'CONTENT_GENERATED'\n};\n\nreturn [{ json: { memoryPackage } }];"
      },
      "typeVersion": 2
    },
    {
      "id": "96eb1393-e879-4dce-8675-4d749b7d49c5",
      "name": "Create Journal PDF Document",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        3520,
        400
      ],
      "parameters": {
        "url": "https://api.example.com/generate-pdf",
        "method": "POST",
        "options": {
          "timeout": 60000
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "template",
              "value": "travel-journal"
            },
            {
              "name": "content",
              "value": "={{ JSON.stringify($json.memoryPackage.journal) }}"
            },
            {
              "name": "photos",
              "value": "={{ JSON.stringify($json.memoryPackage.photos.filter((p, i) => i < 30)) }}"
            },
            {
              "name": "coverPhoto",
              "value": "={{ $json.memoryPackage.photos[0]?.downloadUrl }}"
            }
          ]
        }
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "367cce45-6812-46aa-869d-d36b3349e809",
      "name": "Create Highlights Package",
      "type": "n8n-nodes-base.code",
      "position": [
        3520,
        608
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const memoryPackage = $input.item.json.memoryPackage;\nconst highlightPhotos = memoryPackage.photoAnalysis.highlightPhotos || [];\nconst maxHighlights = memoryPackage.validatedTrip?.outputPreferences?.highlightCount || 10;\n\n// Get top highlights\nconst topHighlights = highlightPhotos\n  .sort((a, b) => b.qualityScore - a.qualityScore)\n  .slice(0, maxHighlights);\n\n// Build highlight reel structure\nconst highlightReel = {\n  title: `${memoryPackage.destination} - Best Moments`,\n  subtitle: `${memoryPackage.durationDays} days of memories`,\n  coverPhoto: memoryPackage.photos[0],\n  \n  slides: topHighlights.map((highlight, index) => {\n    const photo = memoryPackage.photos[highlight.photoIndex];\n    return {\n      slideNumber: index + 1,\n      photo: {\n        url: photo.downloadUrl,\n        thumbnail: photo.thumbnailUrl,\n        filename: photo.filename\n      },\n      caption: highlight.caption,\n      title: highlight.suggestedTitle,\n      description: highlight.significance,\n      category: highlight.category,\n      mood: highlight.mood,\n      date: photo.creationTime.split('T')[0],\n      location: photo.location\n    };\n  }),\n  \n  soundtrack: {\n    mood: memoryPackage.photoAnalysis.tripAnalysis?.overallMood || 'upbeat',\n    suggestions: ['Song suggestion based on mood']\n  },\n  \n  exportFormats: {\n    instagram_story: { duration: 3, format: '1080x1920' },\n    instagram_post: { format: '1080x1080' },\n    youtube_shorts: { duration: 60, format: '1080x1920' },\n    slideshow_video: { duration: 180, format: '1920x1080' }\n  },\n  \n  statistics: {\n    totalHighlights: topHighlights.length,\n    averageQuality: Math.round(topHighlights.reduce((sum, h) => sum + h.qualityScore, 0) / topHighlights.length),\n    categoriesRepresented: [...new Set(topHighlights.map(h => h.category))],\n    moodsRepresented: [...new Set(topHighlights.map(h => h.mood))]\n  }\n};\n\nreturn { json: { \n  memoryPackage,\n  highlightReel \n}};"
      },
      "typeVersion": 2
    },
    {
      "id": "dd0842f6-808c-429f-a9a5-c394341c7556",
      "name": "Save to Google Drive",
      "type": "n8n-nodes-base.googleDrive",
      "position": [
        3520,
        816
      ],
      "parameters": {
        "name": "={{ $json.memoryPackage.tripId }}-journal.json",
        "driveId": {
          "__rl": true,
          "mode": "list",
          "value": "My Drive"
        },
        "options": {},
        "folderId": {
          "__rl": true,
          "mode": "list",
          "value": "root",
          "cachedResultName": "/ (Root folder)"
        }
      },
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 3,
      "continueOnFail": true
    },
    {
      "id": "987ec5fa-d096-4fd7-a553-c82b72d54c54",
      "name": "Store Metadata in Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3520,
        1024
      ],
      "parameters": {
        "columns": {
          "mappingMode": "autoMapInputData"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "generated_content"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5,
      "continueOnFail": true
    },
    {
      "id": "4f91149c-45af-425a-82a5-1c1a330a931f",
      "name": "Send Email with Memory Package",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        3776,
        656
      ],
      "parameters": {
        "options": {
          "attachments": "journal-document,highlights-package"
        },
        "subject": "=\u2728 Your {{ $json.memoryPackage.destination }} Travel Memories Are Ready!",
        "toEmail": "={{ $json.memoryPackage.userEmail }}",
        "fromEmail": "user@example.com"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "b1f2ab40-5d57-439a-b6e1-a40504477a64",
      "name": "Build API Response",
      "type": "n8n-nodes-base.code",
      "position": [
        4000,
        656
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const memoryPackage = $input.item.json.memoryPackage;\nconst highlightReel = $input.item.json.highlightReel;\n\nreturn {\n  json: {\n    success: true,\n    processingId: memoryPackage.processingId,\n    tripId: memoryPackage.tripId,\n    destination: memoryPackage.destination,\n    generatedAt: memoryPackage.generatedAt,\n    \n    summary: {\n      totalPhotos: memoryPackage.statistics.photos.totalPhotos,\n      highlightPhotos: memoryPackage.statistics.highlightPhotosCount,\n      journalPages: memoryPackage.journal.dailyNarratives?.length || 0,\n      reviewsDrafted: memoryPackage.reviews.reviews?.length || 0,\n      qualityScore: memoryPackage.statistics.overallQualityScore\n    },\n    \n    topHighlight: highlightReel.slides[0] || null,\n    \n    deliverables: {\n      journalPDF: 'Available in email attachment',\n      highlightsReel: `${highlightReel.slides.length} slides ready`,\n      reviews: memoryPackage.reviews.reviews?.map(r => r.platform) || [],\n      googleDrive: 'Saved to your Drive folder'\n    },\n    \n    journalPreview: {\n      title: memoryPackage.journal.journalTitle,\n      openingLine: memoryPackage.journal.openingReflection?.substring(0, 200) + '...',\n      favoriteMemories: memoryPackage.journal.favoriteMemories?.slice(0, 3) || []\n    },\n    \n    message: `Your ${memoryPackage.destination} travel memories are ready! Check your email for the complete package.`\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "604a71f7-821a-41bc-9004-a0aa33994b84",
      "name": "Send Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        4448,
        672
      ],
      "parameters": {
        "options": {
          "responseHeaders": {
            "entries": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          }
        },
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify($json, null, 2) }}"
      },
      "typeVersion": 1
    },
    {
      "id": "3838ccc2-ed82-434f-b1df-af0db160a0f9",
      "name": "Daily Batch Processing",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        864,
        1456
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 6 * * *"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "5e6d568b-8816-441c-8701-794c350ed613",
      "name": "Find Recently Completed Trips",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1088,
        1456
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "trips"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "4c03f5ab-6d24-4f48-9803-4c42c75127a6",
      "name": "Filter Trips Needing Processing",
      "type": "n8n-nodes-base.code",
      "position": [
        1360,
        1456
      ],
      "parameters": {
        "jsCode": "const trips = $input.all().map(i => i.json);\nconst today = new Date();\nconst sevenDaysAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);\n\n// Filter trips that:\n// 1. Ended in the last 7 days\n// 2. Haven't been processed yet\n// 3. Have photo data available\nconst tripsToProcess = trips.filter(trip => {\n  const endDate = new Date(trip.endDate);\n  \n  // Skip if trip hasn't ended\n  if (endDate > today) return false;\n  \n  // Skip if trip ended more than 7 days ago\n  if (endDate < sevenDaysAgo) return false;\n  \n  // Skip if already processed\n  if (trip.memoryPackageGenerated === 'true' || trip.memoryPackageGenerated === true) return false;\n  \n  // Skip if no photos indicated\n  if (!trip.hasPhotos || trip.photoCount === 0) return false;\n  \n  return true;\n});\n\n// Create processing jobs\nconst processingJobs = tripsToProcess.map(trip => ({\n  json: {\n    body: {\n      tripId: trip.tripId,\n      userId: trip.userId,\n      userName: trip.userName,\n      userEmail: trip.userEmail,\n      tripDetails: {\n        destination: trip.destination,\n        startDate: trip.startDate,\n        endDate: trip.endDate,\n        tripType: trip.tripType || 'general',\n        mainInterests: trip.mainInterests?.split(',') || []\n      },\n      photoSources: {\n        googlePhotosAlbumId: trip.googlePhotosAlbumId,\n        includeSharedPhotos: true\n      },\n      notesLocation: {\n        googleSheetId: trip.notesSheetId || 'YOUR_GOOGLE_SHEET_ID',\n        sheetTab: 'trip_notes'\n      },\n      outputPreferences: {\n        journalStyle: 'narrative',\n        includeMapRoute: true,\n        reviewPlatforms: ['tripadvisor', 'google', 'instagram'],\n        highlightCount: 10,\n        privacyLevel: 'private'\n      },\n      deliveryOptions: {\n        emailPDF: true,\n        saveToDrive: true\n      }\n    }\n  }\n}));\n\nreturn processingJobs;"
      },
      "typeVersion": 2
    },
    {
      "id": "7b4f79ce-0c2d-46a5-b1e2-7454ef7cc8c3",
      "name": "Log Batch Processing Analytics",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1616,
        1456
      ],
      "parameters": {
        "columns": {
          "mappingMode": "autoMapInputData"
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "analytics"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "7475f9df-2dd4-4b51-ac97-c2c68b8f83d0",
      "name": "Claude Sonnet 4 with Vision1",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        2368,
        864
      ],
      "parameters": {
        "model": "claude-sonnet-4-20250514",
        "options": {
          "temperature": 0.4
        }
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "9a33e174-0c07-4402-881f-55f48febfc70",
      "name": "Claude Sonnet 4 with Vision2",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        2560,
        1264
      ],
      "parameters": {
        "model": "claude-sonnet-4-20250514",
        "options": {
          "temperature": 0.4
        }
      },
      "credentials": {
        "anthropicApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "798dcd3a-483f-4b17-8631-487796565c7b",
      "name": "Wait For While",
      "type": "n8n-nodes-base.wait",
      "position": [
        4224,
        656
      ],
      "parameters": {},
      "typeVersion": 1.1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "412549b8-e0ad-4196-b7c0-6e08ae0cdbef",
  "connections": {
    "Wait For While": {
      "main": [
        [
          {
            "node": "Send Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Trip Notes": {
      "main": [
        [
          {
            "node": "Process Trip Notes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build API Response": {
      "main": [
        [
          {
            "node": "Wait For While",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Trip Notes": {
      "main": [
        [
          {
            "node": "Merge Photos & Notes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Trip Data": {
      "main": [
        [
          {
            "node": "Fetch Google Photos",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Trip Notes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Google Photos": {
      "main": [
        [
          {
            "node": "Extract Photo Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Photos & Notes": {
      "main": [
        [
          {
            "node": "Analyze Photos with Claude Vision",
            "type": "main",
            "index": 0
          },
          {
            "node": "Generate Travel Journal Content",
            "type": "main",
            "index": 0
          },
          {
            "node": "Generate Platform Review Drafts",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse All AI Outputs": {
      "main": [
        [
          {
            "node": "Create Journal PDF Document",
            "type": "main",
            "index": 0
          },
          {
            "node": "Create Highlights Package",
            "type": "main",
            "index": 0
          },
          {
            "node": "Save to Google Drive",
            "type": "main",
            "index": 0
          },
          {
            "node": "Store Metadata in Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save to Google Drive": {
      "main": [
        [
          {
            "node": "Send Email with Memory Package",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily Batch Processing": {
      "main": [
        [
          {
            "node": "Find Recently Completed Trips",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Photo Metadata": {
      "main": [
        [
          {
            "node": "Merge Photos & Notes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trip Completion Webhook": {
      "main": [
        [
          {
            "node": "Validate Trip Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Metadata in Sheets": {
      "main": [
        [
          {
            "node": "Send Email with Memory Package",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Highlights Package": {
      "main": [
        [
          {
            "node": "Send Email with Memory Package",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Sonnet 4 with Vision": {
      "ai_languageModel": [
        [
          {
            "node": "Analyze Photos with Claude Vision",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Create Journal PDF Document": {
      "main": [
        [
          {
            "node": "Send Email with Memory Package",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude Sonnet 4 with Vision1": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Travel Journal Content",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Claude Sonnet 4 with Vision2": {
      "ai_languageModel": [
        [
          {
            "node": "Generate Platform Review Drafts",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Find Recently Completed Trips": {
      "main": [
        [
          {
            "node": "Filter Trips Needing Processing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Email with Memory Package": {
      "main": [
        [
          {
            "node": "Build API Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Trips Needing Processing": {
      "main": [
        [
          {
            "node": "Log Batch Processing Analytics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Platform Review Drafts": {
      "main": [
        [
          {
            "node": "Parse All AI Outputs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Travel Journal Content": {
      "main": [
        [
          {
            "node": "Parse All AI Outputs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Photos with Claude Vision": {
      "main": [
        [
          {
            "node": "Parse All AI Outputs",
            "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 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 scheduled check for recently ended trips Validate Trip Data - Confirms trip…

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

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
AI & RAG

Faceless YouTube Generator. Uses httpRequest, limit, googleDrive, googleSheets. Webhook trigger; 49 nodes.

HTTP Request, Google Drive, Google Sheets +7
AI & RAG

This workflow continuously monitors CVE databases, threat intelligence feeds, and public security advisories to surface emerging zero-day threats, correlates them against your registered infrastructur

Airtable, HTTP Request, Agent +3