This workflow corresponds to n8n.io template #6928 — we link there as the canonical source.
This workflow follows the Agent → Gmail recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"nodes": [
{
"id": "7a561138-c149-4f92-97b7-087771017521",
"name": "Start Workflow",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-608,
288
],
"parameters": {},
"typeVersion": 1
},
{
"id": "acc1a472-587e-4dc2-b215-e986ca907163",
"name": "User Input Configuration",
"type": "n8n-nodes-base.set",
"position": [
-416,
288
],
"parameters": {
"fields": {
"values": [
{
"name": "search_query",
"stringValue": "nh\u00e0 h\u00e0ng qu\u1eadn 1 TP.HCM"
},
{
"name": "search_location",
"stringValue": "@10.8231,106.6297,12z"
},
{
"name": "language_code",
"stringValue": "vi"
},
{
"name": "analysis_focus",
"stringValue": "restaurant"
},
{
"name": "city_name",
"stringValue": "TP. H\u1ed3 Ch\u00ed Minh"
}
]
},
"options": {}
},
"typeVersion": 3.2
},
{
"id": "9c3ced66-5ec2-4e70-8150-e11711bc3f90",
"name": "Dynamic Search Places",
"type": "n8n-nodes-base.httpRequest",
"position": [
-208,
288
],
"parameters": {
"url": "https://serpapi.com/search.json",
"options": {},
"jsonQuery": "={\n \"engine\": \"google_maps\",\n \"q\": \"{{ $('User Input Configuration').item.json.search_query }}\",\n \"ll\": \"{{ $('User Input Configuration').item.json.search_location }}\",\n \"hl\": \"{{ $('User Input Configuration').item.json.language_code }}\",\n \"api_key\": \"ffbea3b40ea41b0e10dbceb1302b81209669f83c5b9b75ed8309dbc3f2ae624d\"\n}",
"sendQuery": true,
"specifyQuery": "json"
},
"typeVersion": 4.2
},
{
"id": "0062335c-2c19-4132-938c-37a7013668ed",
"name": "Enhanced Extract Data",
"type": "n8n-nodes-base.code",
"position": [
-16,
288
],
"parameters": {
"jsCode": "// Enhanced extraction with better filtering and validation\nconst localResults = $input.first().json.local_results || [];\nconst userConfig = $('User Input Configuration').item.json;\n\nconsole.log(`Processing ${localResults.length} search results for: ${userConfig.search_query}`);\n\n// Extract and filter places with more comprehensive data\nconst places = localResults.map(place => {\n // Extract additional data that might be useful for analysis\n const placeData = {\n place_name: place.title || 'Unknown',\n address: place.address || 'No address',\n rating: place.rating || 'No rating',\n reviews_count: place.reviews || 0,\n reviews_link: place.reviews_link || null,\n data_id: place.data_id || null,\n type: place.type || 'Unknown',\n place_id: place.place_id || null,\n lsig: place.lsig || null,\n phone: place.phone || null,\n website: place.website || null,\n thumbnail: place.thumbnail || null,\n operating_hours: place.operating_hours || null,\n price_level: place.price_level || null,\n plus_code: place.plus_code || null,\n coordinates: {\n lat: place.gps_coordinates?.latitude || null,\n lng: place.gps_coordinates?.longitude || null\n },\n // Add context about what we're analyzing\n search_context: {\n original_query: userConfig.search_query,\n analysis_focus: userConfig.analysis_focus,\n city: userConfig.city_name,\n search_timestamp: new Date().toISOString()\n }\n };\n \n return placeData;\n}).filter(place => {\n // Enhanced filtering - keep places that have either reviews_link OR sufficient basic data\n return place.reviews_link || (place.rating && place.rating !== 'No rating') || place.reviews_count > 0;\n});\n\nconsole.log(`Filtered to ${places.length} places with review data or sufficient information`);\nconsole.log(`Analysis focus: ${userConfig.analysis_focus} in ${userConfig.city_name}`);\n\n// Return array of places with enhanced metadata\nreturn places.map(place => ({ json: place }));"
},
"typeVersion": 2
},
{
"id": "136b7156-e38c-43a2-95d5-38a76c8e067a",
"name": "Loop Over Places",
"type": "n8n-nodes-base.splitInBatches",
"position": [
192,
80
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "31112897-af1c-4476-85b9-81a7f287a7ca",
"name": "Get Reviews Content",
"type": "n8n-nodes-base.httpRequest",
"position": [
400,
240
],
"parameters": {
"url": "={{ $json.reviews_link }}",
"options": {
"timeout": 30000,
"allowUnauthorizedCerts": true
},
"jsonQuery": "{\n \"api_key\": \"ffbea3b40ea41b0e10dbceb1302b81209669f83c5b9b75ed8309dbc3f2ae624d\"\n}",
"sendQuery": true,
"specifyQuery": "json"
},
"typeVersion": 4.2
},
{
"id": "c9f7f981-8476-4ba1-8448-5f29ae13d989",
"name": "Enhanced Combine Data",
"type": "n8n-nodes-base.code",
"position": [
592,
384
],
"parameters": {
"jsCode": "// Enhanced data combination with error handling\nconst currentItem = $('Loop Over Places').item;\nconst reviewsResponse = $input.first().json;\nconst userConfig = $('User Input Configuration').item.json;\n\n// Enhanced combined data with more comprehensive information\nconst combinedData = {\n // Basic place information\n place_name: currentItem.json.place_name,\n address: currentItem.json.address,\n rating: currentItem.json.rating,\n reviews_count: currentItem.json.reviews_count,\n data_id: currentItem.json.data_id,\n type: currentItem.json.type,\n phone: currentItem.json.phone,\n website: currentItem.json.website,\n coordinates: currentItem.json.coordinates,\n operating_hours: currentItem.json.operating_hours,\n price_level: currentItem.json.price_level,\n \n // Reviews content with error handling\n reviews_content: reviewsResponse || { error: 'No reviews data available' },\n \n // Enhanced metadata for analysis\n analysis_metadata: {\n search_query: userConfig.search_query,\n analysis_focus: userConfig.analysis_focus,\n target_city: userConfig.city_name,\n data_collection_time: new Date().toISOString(),\n has_reviews: !!(reviewsResponse && reviewsResponse.reviews),\n review_source: currentItem.json.reviews_link || 'No review link',\n place_completeness_score: calculateCompletenessScore(currentItem.json)\n }\n};\n\n// Function to calculate how complete the place data is\nfunction calculateCompletenessScore(placeData) {\n const fields = ['place_name', 'address', 'rating', 'phone', 'website', 'operating_hours'];\n const filledFields = fields.filter(field => \n placeData[field] && placeData[field] !== 'Unknown' && placeData[field] !== 'No address'\n );\n return Math.round((filledFields.length / fields.length) * 100);\n}\n\nconsole.log(`Processing ${combinedData.analysis_metadata.analysis_focus}: ${combinedData.place_name}`);\nconsole.log(`Data completeness: ${combinedData.analysis_metadata.place_completeness_score}%`);\n\nreturn { json: combinedData };"
},
"typeVersion": 2
},
{
"id": "68e789cd-2df5-4783-8891-5cd4d6eb0553",
"name": "Collect All Data",
"type": "n8n-nodes-base.aggregate",
"position": [
800,
288
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData"
},
"typeVersion": 1
},
{
"id": "d1084cb2-0d34-4f6a-8a51-f87b1c01c6c3",
"name": "Prepare Analysis Data",
"type": "n8n-nodes-base.code",
"position": [
992,
288
],
"parameters": {
"jsCode": "// Enhanced data preparation with flexible analysis structure\nconst allPlaces = $input.all();\nconst userConfig = $('User Input Configuration').item.json;\n\nconsole.log(`Preparing analysis for ${allPlaces.length} ${userConfig.analysis_focus} locations in ${userConfig.city_name}`);\n\n// Create comprehensive analysis dataset\nlet analysisPrompt = `MARKET RESEARCH DATA ANALYSIS\\n`;\nanalysisPrompt += `Research Focus: ${userConfig.analysis_focus}\\n`;\nanalysisPrompt += `Location: ${userConfig.city_name}\\n`;\nanalysisPrompt += `Search Query: \"${userConfig.search_query}\"\\n`;\nanalysisPrompt += `Total Locations Found: ${allPlaces.length}\\n`;\nanalysisPrompt += `Analysis Date: ${new Date().toLocaleDateString('vi-VN')}\\n\\n`;\n\n// Enhanced data summary\nanalysisPrompt += `=== DATASET OVERVIEW ===\\n`;\nlet totalReviews = 0;\nlet avgRating = 0;\nlet placesWithReviews = 0;\nlet completenessScores = [];\n\nallPlaces.forEach((place, index) => {\n const data = place.json;\n \n // Calculate statistics\n if (data.reviews_count && typeof data.reviews_count === 'number') {\n totalReviews += data.reviews_count;\n }\n \n if (data.rating && data.rating !== 'No rating') {\n avgRating += parseFloat(data.rating) || 0;\n }\n \n if (data.reviews_content && data.reviews_content.reviews) {\n placesWithReviews++;\n }\n \n if (data.analysis_metadata && data.analysis_metadata.place_completeness_score) {\n completenessScores.push(data.analysis_metadata.place_completeness_score);\n }\n \n // Add detailed place information\n analysisPrompt += `\\n${index + 1}. ${data.place_name}\\n`;\n analysisPrompt += ` Address: ${data.address}\\n`;\n analysisPrompt += ` Rating: ${data.rating}/5 (${data.reviews_count || 0} reviews)\\n`;\n \n // Add contact and operational info if available\n if (data.phone) analysisPrompt += ` Phone: ${data.phone}\\n`;\n if (data.website) analysisPrompt += ` Website: ${data.website}\\n`;\n if (data.operating_hours) analysisPrompt += ` Hours: ${JSON.stringify(data.operating_hours)}\\n`;\n if (data.price_level) analysisPrompt += ` Price Level: ${data.price_level}\\n`;\n \n // Add review samples if available\n if (data.reviews_content && data.reviews_content.reviews) {\n analysisPrompt += ` Recent Reviews:\\n`;\n data.reviews_content.reviews.slice(0, 5).forEach(review => {\n const reviewText = review.snippet || review.text || review.comment || 'No text available';\n const reviewRating = review.rating || 'No rating';\n analysisPrompt += ` - \"${reviewText.substring(0, 200)}...\" (${reviewRating}/5)\\n`;\n });\n }\n \n analysisPrompt += ` Data Completeness: ${data.analysis_metadata?.place_completeness_score || 'N/A'}%\\n`;\n analysisPrompt += `\\n${'-'.repeat(60)}\\n`;\n});\n\n// Add summary statistics\nconst avgRatingCalculated = allPlaces.length > 0 ? (avgRating / allPlaces.length).toFixed(2) : 0;\nconst avgCompleteness = completenessScores.length > 0 ? \n (completenessScores.reduce((a, b) => a + b, 0) / completenessScores.length).toFixed(1) : 'N/A';\n\nanalysisPrompt += `\\n=== SUMMARY STATISTICS ===\\n`;\nanalysisPrompt += `Total Places Analyzed: ${allPlaces.length}\\n`;\nanalysisPrompt += `Places with Review Data: ${placesWithReviews}\\n`;\nanalysisPrompt += `Total Reviews Collected: ${totalReviews}\\n`;\nanalysisPrompt += `Average Rating: ${avgRatingCalculated}/5\\n`;\nanalysisPrompt += `Average Data Completeness: ${avgCompleteness}%\\n`;\n\nreturn {\n json: {\n analysis_prompt: analysisPrompt,\n raw_data: allPlaces.map(place => place.json),\n summary_stats: {\n total_places: allPlaces.length,\n places_with_reviews: placesWithReviews,\n total_reviews: totalReviews,\n average_rating: avgRatingCalculated,\n average_completeness: avgCompleteness,\n search_context: userConfig\n }\n }\n};"
},
"typeVersion": 2
},
{
"id": "0cdd33f4-447d-4246-98bf-410e562686c1",
"name": "AI Market Analysis",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1168,
288
],
"parameters": {
"text": "=B\u1ea1n l\u00e0 chuy\u00ean gia ph\u00e2n t\u00edch v\u00e0 nghi\u00ean c\u1ee9u th\u1ecb tr\u01b0\u1eddng chuy\u00ean s\u00e2u v\u1edbi kh\u1ea3 n\u0103ng t\u1ea1o ra c\u00e1c b\u00e1o c\u00e1o insights chi ti\u1ebft v\u00e0 actionable.\n\nB\u1ea1n \u0111\u00e3 nh\u1eadn \u0111\u01b0\u1ee3c d\u1eef li\u1ec7u th\u1ecb tr\u01b0\u1eddng th\u1ef1c t\u1ebf v\u1ec1 \"{{ $('User Input Configuration').item.json.analysis_focus }}\" t\u1ea1i {{ $('User Input Configuration').item.json.city_name }} v\u1edbi \u0111\u1ea7y \u0111\u1ee7 th\u00f4ng tin t\u1eeb kh\u00e1ch h\u00e0ng th\u1ef1c t\u1ebf.\n\n=== D\u1eee LI\u1ec6U PH\u00c2N T\u00cdCH ===\n{{ JSON.stringify($json.raw_data, null, 2) }}\n\n=== NHI\u1ec6M V\u1ee4 PH\u00c2N T\u00cdCH ===\n\nH\u00e3y t\u1ea1o m\u1ed9t b\u00e1o c\u00e1o ph\u00e2n t\u00edch th\u1ecb tr\u01b0\u1eddng to\u00e0n di\u1ec7n v\u1edbi c\u1ea5u tr\u00fac sau:\n\n**I. T\u00d3M T\u1eaeT \u0110I\u1ec0U H\u00c0NH (EXECUTIVE SUMMARY)**\n- Insights ch\u00ednh trong 3-4 c\u00e2u\n- C\u01a1 h\u1ed9i kinh doanh l\u1edbn nh\u1ea5t\n- Khuy\u1ebfn ngh\u1ecb h\u00e0nh \u0111\u1ed9ng ngay l\u1eadp t\u1ee9c\n\n**II. PH\u00c2N T\u00cdCH T\u1ed4NG QUAN TH\u1eca TR\u01af\u1edcNG**\n- Quy m\u00f4 v\u00e0 m\u1eadt \u0111\u1ed9 th\u1ecb tr\u01b0\u1eddng {{ $('User Input Configuration').item.json.analysis_focus }} t\u1ea1i {{ $('User Input Configuration').item.json.city_name }}\n- Ph\u00e2n kh\u00fac gi\u00e1 v\u00e0 ch\u1ea5t l\u01b0\u1ee3ng d\u1ecbch v\u1ee5\n- Ph\u00e2n b\u1ed1 \u0111\u1ecba l\u00fd v\u00e0 khu v\u1ef1c hot\n- M\u1ee9c \u0111\u1ed9 c\u1ea1nh tranh v\u00e0 gap th\u1ecb tr\u01b0\u1eddng\n\n**III. PH\u00c2N T\u00cdCH TR\u1ea2I NGHI\u1ec6M KH\u00c1CH H\u00c0NG**\n- Top 5 y\u1ebfu t\u1ed1 \u0111\u01b0\u1ee3c \u0111\u00e1nh gi\u00e1 cao nh\u1ea5t (v\u1edbi % kh\u00e1ch h\u00e0ng mention)\n- Top 5 v\u1ea5n \u0111\u1ec1 ph\u1ed5 bi\u1ebfn nh\u1ea5t (v\u1edbi t\u1ea7n su\u1ea5t xu\u1ea5t hi\u1ec7n)\n- Sentiment analysis t\u1ed5ng th\u1ec3\n- Customer journey v\u00e0 pain points ch\u00ednh\n\n**IV. COMPETITIVE LANDSCAPE**\n- Ph\u00e2n t\u00edch 3-5 player m\u1ea1nh nh\u1ea5t\n- \u0110i\u1ec3m m\u1ea1nh/y\u1ebfu c\u1ee7a t\u1eebng competitor\n- Pricing strategy v\u00e0 value proposition\n- Market positioning gaps\n\n**V. INSIGHTS V\u00c0 XU H\u01af\u1edaNG**\n- Behavioral patterns c\u1ee7a target customers\n- Emerging trends v\u00e0 changing preferences\n- Seasonal/temporal patterns n\u1ebfu c\u00f3\n- Technology adoption v\u00e0 digital readiness\n\n**VI. KHUY\u1ebeN NGH\u1eca CHI\u1ebeN L\u01af\u1ee2C**\n- Business model t\u1ed1i \u01b0u cho th\u1ecb tr\u01b0\u1eddng n\u00e0y\n- Pricing strategy v\u00e0 revenue streams\n- Marketing approach v\u00e0 customer acquisition\n- Operational excellence priorities\n- Investment areas v\u00e0 resource allocation\n\n**VII. ACTION PLAN**\n- 3 h\u00e0nh \u0111\u1ed9ng ngay l\u1eadp t\u1ee9c (next 30 days)\n- 3 initiatives trung h\u1ea1n (3-6 months)\n- Long-term strategy (6-12 months)\n\nY\u00eau c\u1ea7u:\n- S\u1eed d\u1ee5ng s\u1ed1 li\u1ec7u c\u1ee5 th\u1ec3 t\u1eeb d\u1eef li\u1ec7u th\u1ef1c t\u1ebf\n- \u0110\u01b0a ra insights \u0111\u1ed9c \u0111\u00e1o, kh\u00f4ng ch\u1ec9 m\u00f4 t\u1ea3\n- T\u1eadp trung v\u00e0o actionable recommendations\n- Vi\u1ebft b\u1eb1ng ti\u1ebfng Vi\u1ec7t chuy\u00ean nghi\u1ec7p nh\u01b0ng d\u1ec5 hi\u1ec3u\n- Highlight key metrics v\u00e0 success factors\n- \u0110\u1ec1 xu\u1ea5t KPIs \u0111\u1ec3 theo d\u00f5i success",
"options": {},
"promptType": "define"
},
"typeVersion": 2.1
},
{
"id": "82c701cb-a88a-410c-bda4-aaaf4c5227ef",
"name": "Google Gemini Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
1152,
480
],
"parameters": {
"options": {}
},
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "2ec72fea-962e-45d4-aa48-abf02c0d80de",
"name": "Prepare Email Content",
"type": "n8n-nodes-base.code",
"position": [
1536,
176
],
"parameters": {
"jsCode": "// Simplified email preparation - Analysis content only\nconst reportContent = $input.first().json.output;\nconst userConfig = $('User Input Configuration').item.json;\nconst summaryStats = $('Prepare Analysis Data').item.json.summary_stats;\n\n// Generate dynamic subject based on analysis focus\nconst analysisType = userConfig.analysis_focus;\nconst cityName = userConfig.city_name;\nconst currentDate = new Date().toLocaleDateString('vi-VN');\n\nconst emailSubject = `\ud83d\udcca B\u00e1o C\u00e1o Ph\u00e2n T\u00edch Th\u1ecb Tr\u01b0\u1eddng ${analysisType} ${cityName} - ${currentDate}`;\n\n// Enhanced HTML formatting function\nfunction formatToHTML(content) {\n let html = content\n // Convert markdown-style formatting\n .replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>')\n .replace(/\\*([^*]+)\\*/g, '<em>$1</em>')\n \n // Convert main sections (I., II., III., etc.)\n .replace(/\\*\\*([IVX]+\\. [^*]+)\\*\\*/g, '<h2 style=\"color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 8px; margin-top: 30px;\">$1</h2>')\n \n // Convert subsections\n .replace(/\\*\\*([^*]+:)\\*\\*/g, '<h3 style=\"color: #34495e; margin-top: 20px; margin-bottom: 10px;\">$1</h3>')\n \n // Convert numbered lists with enhanced styling\n .replace(/^(\\d+\\. )/gm, '<div style=\"margin: 8px 0;\"><strong style=\"color: #3498db;\">$1</strong>')\n \n // Convert bullet points with different levels\n .replace(/^- /gm, '<div style=\"margin-left: 20px; margin: 5px 0;\">\u2022 ')\n .replace(/^ - /gm, '<div style=\"margin-left: 40px; margin: 5px 0;\">\u25e6 ')\n .replace(/^ - /gm, '<div style=\"margin-left: 60px; margin: 5px 0;\">\u25aa ')\n \n // Close div tags and handle line breaks\n .replace(/(\u2022 [^<\\n]+)(?=\\n|$)/g, '$1</div>')\n .replace(/(\u25e6 [^<\\n]+)(?=\\n|$)/g, '$1</div>')\n .replace(/(\u25aa [^<\\n]+)(?=\\n|$)/g, '$1</div>')\n .replace(/\\n\\n/g, '</p><p>')\n .replace(/\\n/g, '<br>');\n\n return '<p>' + html + '</p>'.replace(/<p><\\/p>/g, '');\n}\n\n// Create comprehensive HTML email template - Analysis focused\nconst htmlContent = `\n<!DOCTYPE html>\n<html lang=\"vi\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${emailSubject}</title>\n <style>\n body {\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n line-height: 1.6;\n color: #333;\n max-width: 900px;\n margin: 0 auto;\n padding: 20px;\n background-color: #f8f9fa;\n }\n .container {\n background-color: white;\n padding: 40px;\n border-radius: 12px;\n box-shadow: 0 4px 20px rgba(0,0,0,0.1);\n }\n .header {\n text-align: center;\n margin-bottom: 40px;\n padding: 30px;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n border-radius: 10px;\n }\n .header h1 {\n margin: 0;\n font-size: 26px;\n font-weight: bold;\n }\n .header .subtitle {\n font-size: 16px;\n margin-top: 10px;\n opacity: 0.95;\n }\n .stats-overview {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 20px;\n margin: 30px 0;\n padding: 25px;\n background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);\n border-radius: 10px;\n }\n .stat-item {\n text-align: center;\n padding: 15px;\n background: white;\n border-radius: 8px;\n box-shadow: 0 2px 10px rgba(0,0,0,0.1);\n }\n .stat-number {\n font-size: 24px;\n font-weight: bold;\n color: #3498db;\n }\n .stat-label {\n font-size: 12px;\n color: #7f8c8d;\n margin-top: 5px;\n }\n h2 {\n color: #2c3e50;\n border-bottom: 2px solid #3498db;\n padding-bottom: 8px;\n margin-top: 30px;\n }\n h3 {\n color: #34495e;\n margin-top: 20px;\n margin-bottom: 10px;\n }\n .highlight {\n background: #fff3cd;\n border: 1px solid #ffeaa7;\n padding: 15px;\n border-radius: 5px;\n margin: 15px 0;\n }\n .footer {\n margin-top: 40px;\n padding: 25px;\n background: #34495e;\n color: white;\n text-align: center;\n border-radius: 10px;\n font-size: 14px;\n }\n @media (max-width: 600px) {\n body { padding: 10px; }\n .container { padding: 20px; }\n .stats-overview { grid-template-columns: 1fr; }\n }\n </style>\n</head>\n<body>\n <div class=\"container\">\n <div class=\"header\">\n <h1>\ud83d\udcca B\u00c1O C\u00c1O PH\u00c2N T\u00cdCH TH\u1eca TR\u01af\u1edcNG</h1>\n <div class=\"subtitle\">${analysisType} t\u1ea1i ${cityName}</div>\n <div style=\"font-size: 14px; margin-top: 15px; opacity: 0.9;\">\n Ng\u00e0y ph\u00e2n t\u00edch: ${new Date().toLocaleDateString('vi-VN', { \n year: 'numeric', \n month: 'long', \n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit'\n })}\n </div>\n </div>\n \n ${formatToHTML(reportContent)}\n \n <div class=\"footer\">\n <p><strong>\ud83d\ude80 Market Research Analytics System</strong></p>\n <p>\ud83d\udce7 B\u00e1o c\u00e1o \u0111\u01b0\u1ee3c t\u1ea1o t\u1ef1 \u0111\u1ed9ng t\u1eeb d\u1eef li\u1ec7u th\u1ef1c t\u1ebf \n <p>\ud83c\udfaf Ph\u00e2n t\u00edch: ${analysisType} | \ud83d\udccd \u0110\u1ecba \u0111i\u1ec3m: ${cityName}</p>\n <p style=\"margin-top: 15px; font-size: 12px; opacity: 0.8;\">\n Query g\u1ed1c: \"${userConfig.search_query}\"\n </p>\n </div>\n </div>\n</body>\n</html>\n`;\n\n// Create simplified plain text version - Analysis only\nconst plainTextContent = `\nB\u00c1O C\u00c1O PH\u00c2N T\u00cdCH TH\u1eca TR\u01af\u1edcNG - ${analysisType.toUpperCase()} ${cityName.toUpperCase()}\n${'='.repeat(60)}\n\nS\u1ed0 LI\u1ec6U T\u1ed4NG QUAN:\n- \u0110\u1ecba \u0111i\u1ec3m ph\u00e2n t\u00edch: ${summaryStats.total_places}\n- T\u1ed5ng \u0111\u00e1nh gi\u00e1: ${summaryStats.total_reviews}\n- Rating TB: ${summaryStats.average_rating}/5\n- C\u00f3 data chi ti\u1ebft: ${summaryStats.places_with_reviews}\n\n${reportContent.replace(/\\*\\*([^*]+)\\*\\*/g, '$1').replace(/\\*/g, '')}\n\n${'='.repeat(60)}\nB\u00e1o c\u00e1o t\u1ef1 \u0111\u1ed9ng - ${currentDate}\n`;\n\nreturn {\n json: {\n htmlContent: htmlContent,\n plainTextContent: plainTextContent,\n subject: emailSubject,\n analysis_metadata: {\n analysis_type: analysisType,\n city: cityName,\n total_locations: summaryStats.total_places,\n total_reviews: summaryStats.total_reviews,\n average_rating: summaryStats.average_rating,\n search_query: userConfig.search_query,\n generation_time: new Date().toISOString()\n }\n }};"
},
"typeVersion": 2
},
{
"id": "7dd14586-d25d-4b12-baef-2be0c6987456",
"name": "Send Email Report",
"type": "n8n-nodes-base.gmail",
"position": [
1728,
384
],
"parameters": {
"sendTo": "user@example.com",
"message": "={{ $json.htmlContent }}",
"options": {
"ccList": "",
"bccList": "",
"replyTo": ""
},
"subject": "={{ $json.subject }}"
},
"credentials": {
"gmailOAuth2": {
"name": "<your credential>"
}
},
"typeVersion": 2
},
{
"id": "1ecc0e85-5ec2-4f9f-a6e8-23a3f7f826a8",
"name": "Final Status & Logging",
"type": "n8n-nodes-base.code",
"position": [
1936,
384
],
"parameters": {
"jsCode": "// Final workflow status and comprehensive logging\nconst emailResult = $input.first().json;\nconst userConfig = $('User Input Configuration').item.json;\nconst summaryStats = $('Prepare Analysis Data').item.json.summary_stats;\nconst analysisMetadata = $('Prepare Email Content').item.json.analysis_metadata;\n\nconst completionTime = new Date().toISOString();\n\nconsole.log('\ud83c\udf89 ========= FLEXIBLE MARKET ANALYSIS COMPLETED =========');\nconsole.log(`\ud83d\udcca Analysis Type: ${userConfig.analysis_focus}`);\nconsole.log(`\ud83c\udfd9\ufe0f Target City: ${userConfig.city_name}`);\nconsole.log(`\ud83d\udd0d Search Query: \"${userConfig.search_query}\"`);\nconsole.log(`\ud83d\udccd Total Locations Analyzed: ${summaryStats.total_places}`);\nconsole.log(`\ud83d\udcac Total Reviews Processed: ${summaryStats.total_reviews}`);\nconsole.log(`\u2b50 Average Rating: ${summaryStats.average_rating}/5`);\nconsole.log(`\ud83d\udce7 Email Report Sent Successfully`);\nconsole.log(`\u23f0 Completion Time: ${completionTime}`);\nconsole.log('YOUR_AWS_SECRET_KEY_HERE=================');\n\n// Generate comprehensive final report\nconst workflowSummary = {\n workflow_status: 'COMPLETED_SUCCESSFULLY',\n workflow_type: 'FLEXIBLE_MARKET_RESEARCH_ANALYSIS',\n execution_metadata: {\n analysis_focus: userConfig.analysis_focus,\n target_location: userConfig.city_name,\n search_query: userConfig.search_query,\n language_code: userConfig.language_code,\n completion_time: completionTime\n },\n data_collection_results: {\n total_places_found: summaryStats.total_places,\n places_with_review_data: summaryStats.places_with_reviews,\n total_reviews_collected: summaryStats.total_reviews,\n average_rating_calculated: summaryStats.average_rating,\n data_completeness_average: summaryStats.average_completeness\n },\n analysis_outputs: {\n ai_model_used: 'Google_Gemini',\n report_format: 'Comprehensive_Market_Analysis',\n email_delivery_status: emailResult.messageId ? 'SUCCESS' : 'UNKNOWN',\n email_subject: analysisMetadata.subject,\n email_recipient: 'user@example.com'\n },\n workflow_capabilities: {\n flexible_search_query: true,\n dynamic_location_targeting: true,\n multi_language_support: true,\n comprehensive_data_extraction: true,\n ai_powered_analysis: true,\n automated_report_generation: true,\n email_delivery: true\n },\n next_steps_recommendations: [\n 'Review the detailed market analysis report in your email',\n 'Customize search parameters for different market segments',\n 'Set up recurring analysis for market monitoring',\n 'Expand analysis to additional cities or business categories',\n 'Integrate insights into business strategy planning'\n ]\n};\n\nreturn { json: workflowSummary };"
},
"typeVersion": 2
},
{
"id": "599485d6-b773-4462-83d4-e0124c6450d6",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-832,
-1248
],
"parameters": {
"width": 944,
"height": 1392,
"content": "# \ud83d\ude80 Market Research Analytics System\n\n> **Transform Google Maps data into actionable business insights with AI-powered analysis**\n\n## \ud83d\udccb Overview\n\nThis n8n workflow automatically collects business data from Google Maps, analyzes customer reviews using AI, and generates comprehensive market research reports delivered straight to your inbox.\n\n---\n\n## \u26a1 Quick Start Guide\n\n### Step 1: Configure Your Search Parameters\n\nNavigate to the **\"Configuration Variables\"** node and update these fields:\n\n```json\n{\n \"search_query\": \"restaurants downtown\", // Your target business type + location\n \"search_location\": \"@40.7589,-73.9851,12z\", // Coordinates (lat,lng,zoom)\n \"language_code\": \"en\", // Language: en, vi, es, fr, etc.\n \"analysis_focus\": \"restaurant\", // Business category for analysis\n \"city_name\": \"New York City\" // Target city name\n}\n```\n\n**\ud83d\udd0d Need coordinates?** Use [LatLong.net](https://www.latlong.net/) to find your target location coordinates.\n\n---\n\n### Step 2: Setup API Keys\n\n#### \ud83d\udd11 SerpAPI (Google Maps Data)\n1. **Get API Key:** Visit [SerpAPI Google Maps Reviews](https://serpapi.com/google-maps-reviews-api)\n2. **Sign up** for free account (100 searches/month included)\n3. **Copy your API key** from the dashboard\n4. **Update workflow:** Replace `YOUR_SERPAPI_KEY_HERE` in both HTTP nodes\n\n#### \ud83e\udd16 Google Gemini AI (Analysis)\n1. **Get API Key:** Visit [Google AI Studio](https://ai.google.dev/gemini-api/docs/api-key)\n2. **Create new API key** (free tier available)\n3. **Setup in n8n:** Add credentials in \"Google Gemini Chat Model\" node\n\n---\n\n### Step 3: Configure Email Delivery\n\n1. **Open** the **\"Send Email Report\"** node\n2. **Update recipient:** Change `your-email@example.com` to your email\n3. **Gmail setup:** Connect your Gmail account in n8n credentials\n\n---\n\n### Step 4: Execute & Enjoy! \ud83c\udf89"
},
"typeVersion": 1
}
],
"connections": {
"Start Workflow": {
"main": [
[
{
"node": "User Input Configuration",
"type": "main",
"index": 0
}
]
]
},
"Collect All Data": {
"main": [
[
{
"node": "Prepare Analysis Data",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Places": {
"main": [
[
{
"node": "Collect All Data",
"type": "main",
"index": 0
}
],
[
{
"node": "Get Reviews Content",
"type": "main",
"index": 0
}
]
]
},
"Send Email Report": {
"main": [
[
{
"node": "Final Status & Logging",
"type": "main",
"index": 0
}
]
]
},
"AI Market Analysis": {
"main": [
[
{
"node": "Prepare Email Content",
"type": "main",
"index": 0
}
]
]
},
"Get Reviews Content": {
"main": [
[
{
"node": "Enhanced Combine Data",
"type": "main",
"index": 0
}
]
]
},
"Dynamic Search Places": {
"main": [
[
{
"node": "Enhanced Extract Data",
"type": "main",
"index": 0
}
]
]
},
"Enhanced Combine Data": {
"main": [
[
{
"node": "Loop Over Places",
"type": "main",
"index": 0
}
]
]
},
"Enhanced Extract Data": {
"main": [
[
{
"node": "Loop Over Places",
"type": "main",
"index": 0
}
]
]
},
"Prepare Analysis Data": {
"main": [
[
{
"node": "AI Market Analysis",
"type": "main",
"index": 0
}
]
]
},
"Prepare Email Content": {
"main": [
[
{
"node": "Send Email Report",
"type": "main",
"index": 0
}
]
]
},
"Google Gemini Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Market Analysis",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"User Input Configuration": {
"main": [
[
{
"node": "Dynamic Search Places",
"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.
gmailOAuth2googlePalmApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
> Transform Google Maps data into actionable business insights with AI-powered analysis
Source: https://n8n.io/workflows/6928/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
Transform your salon/service business with this streamlined WhatsApp automation system featuring Claude integration, zero-setup database management, and intelligent conversation handling. Claude MCP I
🤖🧑💻 AI Agent for Top n8n Creators Leaderboard Reporting. Uses httpRequest, lmChatOpenAi, executeWorkflowTrigger, toolWorkflow. Event-driven trigger; 49 nodes.
🤖🧑💻 AI Agent for Top n8n Creators Leaderboard Reporting. Uses httpRequest, lmChatOpenAi, executeWorkflowTrigger, toolWorkflow. Event-driven trigger; 49 nodes.
This n8n workflow is designed to automate the aggregation, processing, and reporting of community statistics related to n8n creators and workflows. Its primary purpose is to generate insightful report
This workflow implements an AI-powered WhatsApp booking assistant for a hair salon. The system allows customers to book, reschedule, or cancel appointments automatically via text or voice messages on