Short Explanation
This project is an intelligent email outreach automation system built with N8N that combines website performance analysis with AI-generated personalized emails. The system automatically analyzes potential clients' websites, determines the appropriate outreach strategy based on their site's performance score, and sends tailored emails that address their specific needs.
The goal was to create a scalable outreach solution that feels personal rather than spammy, by using real data about each prospect's website to craft relevant messages.
Project Goals
The main objectives of this automation system were:
- Automate lead qualification: Use Google PageSpeed Insights to objectively assess website quality
- Personalize at scale: Generate unique emails for each prospect using AI, avoiding generic templates
- Track everything: Maintain a complete record of all outreach in Notion for follow-up management
- Smart segmentation: Automatically categorize leads and apply different messaging strategies based on their website score
How It Works
The workflow follows a sophisticated pipeline:
1. Lead Retrieval
The system pulls leads from a Notion database, filtering for contacts that haven't been emailed yet and have valid email addresses. Leads are segmented by sector for targeted messaging.
2. Website Analysis
Each lead's website is analyzed using the Google PageSpeed Insights API. The system extracts:
- Overall performance score (0-100)
- Largest Contentful Paint (LCP) timing
- A screenshot of the website for attachment
3. Smart Segmentation
Based on the performance score, leads are routed to different AI prompts:
- Score < 50 (Redesign): Website has serious issues - messaging focuses on the problem and offers solutions
- Score 50-70 (Optimization): Website is okay but has room for improvement - balanced messaging
- Score 70+ (Upsell): Website is good - compliment their work and offer additional services
4. AI Email Generation
GPT-4.1-mini generates unique emails using structured prompts that include:
- Company name and description
- Actual performance metrics
- Randomized subject lines and opening hooks
- Relevant case studies as social proof
The AI outputs structured JSON ensuring consistent formatting while maintaining natural variation between emails.
5. Email Delivery
Emails are sent via Brevo (SendInBlue) with:
- The AI-generated personalized content
- A screenshot of their website attached
- Proper tracking tags for analytics
6. Database Updates
After each email, the Notion database is updated with:
- Email sent status and timestamp
- The full email body for reference
- Website score and template used
- Daily send count tracking
7. Rate Limiting
To avoid spam filters and maintain deliverability, the system includes random delays (30-70 seconds) between emails.
Tech Stack Used
The system is built entirely in N8N, leveraging its visual workflow builder and extensive integration library:
- N8N: Workflow automation platform handling the entire pipeline
- Notion API: Lead database and CRM functionality
- Google PageSpeed Insights API: Website performance analysis
- OpenAI GPT-4.1-mini: AI-powered email generation with structured output
- Brevo (SendInBlue): Transactional email delivery with tracking
- JavaScript Code Nodes: Custom logic for data transformation and template selection
Key Features
- Three-tier messaging strategy: Different approaches for poor, average, and good websites
- Real data personalization: Every email includes actual metrics from their website
- Attachment automation: Website screenshots automatically captured and attached
- Complete audit trail: Full tracking in Notion for follow-up campaigns
- Anti-spam measures: Random delays and proper email formatting
- Scalable architecture: Can process hundreds of leads automatically
N8N Workflow JSON
You can import this workflow directly into your N8N instance. Make sure to update the credentials for Notion, OpenAI, and Brevo before running.
{
"name": "Email 1",
"nodes": [
{
"parameters": {
"resource": "databasePage",
"operation": "getAll",
"databaseId": {
"__rl": true,
"value": "YOUR_NOTION_DATABASE_ID",
"mode": "list",
"cachedResultName": "Emails Send"
},
"filterType": "manual",
"matchType": "allFilters",
"filters": {
"conditions": [
{
"key": "Emails send|number",
"condition": "is_empty"
},
{
"key": "Email|email",
"condition": "is_not_empty"
},
{
"key": "Last Email|date",
"condition": "is_empty"
},
{
"key": "Sector|select",
"condition": "equals",
"selectValue": "Health"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [-880, 880],
"name": "Get many database pages",
"credentials": {
"notionApi": {
"id": "YOUR_CREDENTIAL_ID",
"name": "Notion account"
}
}
},
{
"parameters": {},
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [-1120, 880],
"name": "When clicking 'Execute workflow'"
},
{
"parameters": {
"url": "https://www.googleapis.com/pagespeedonline/v5/runPagespeed",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "key",
"value": "YOUR_PAGESPEED_API_KEY"
},
{
"name": "url",
"value": "={{ $json.property_url }}"
},
{
"name": "strategy",
"value": "mobile"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [-400, 896],
"name": "HTTP Request",
"retryOnFail": true,
"onError": "continueErrorOutput"
},
{
"parameters": {
"jsCode": "const score = $input.first().json.lighthouseResult.categories.performance.score * 100;\nlet template;\n\nif (score < 50) {\n template = \"email_1_redesign\";\n} else if (score < 70) {\n template = \"email_1_optimization\"; \n} else {\n template = \"email_1_upsell\";\n}\n\nreturn {\n score: score,\n template: template\n}"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [-160, 880],
"name": "Score Classification"
},
{
"parameters": {
"url": "={{ $('Loop Over Items').item.json.property_url }}",
"options": {}
},
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [48, 880],
"name": "Fetch Website HTML",
"onError": "continueRegularOutput"
},
{
"parameters": {
"jsCode": "const response = $input.first().json;\nlet description = \"No description found\";\n\nif (typeof response === 'string') {\n const metaMatch = response.match(/<meta name=\"description\" content=\"([^\"]+)\"/i);\n description = metaMatch ? metaMatch[1] : \"No description found\";\n} else if (typeof response === 'object') {\n description = response.data?.meta_title \n || response.meta_title \n || response.description \n || \"No description found\";\n}\n\nreturn { description };"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [320, 880],
"name": "Extract Meta Description"
},
{
"parameters": {
"rules": {
"values": [
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $('Score Classification').item.json.template }}",
"rightValue": "email_1_redesign",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
}
},
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $('Score Classification').item.json.template }}",
"rightValue": "email_1_optimization",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
}
},
{
"conditions": {
"conditions": [
{
"leftValue": "={{ $('Score Classification').item.json.template }}",
"rightValue": "email_1_upsell",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
}
}
]
},
"options": {}
},
"type": "n8n-nodes-base.switch",
"typeVersion": 3.3,
"position": [512, 864],
"name": "Route by Score"
},
{
"parameters": {
"promptType": "define",
"text": "You are an expert copywriter for high-end web development sales.\n\nDATA:\nCompany: {{ $('Loop Over Items').item.json.property_name }}\nURL: {{ $('Loop Over Items').item.json.property_url }}\nLCP_ms: {{ $('HTTP Request').item.json.lighthouseResult.audits['largest-contentful-paint'].numericValue }}\nScore: {{ $('Score Classification').item.json.score }}\nSector: {{ $('Loop Over Items').item.json.property_sector }}\nDescription: {{ $('Extract Meta Description').item.json.description }}\n\nCONTEXT:\nScore 50-70 = moderate site. Start with compliment + acknowledge site is \"okay\".\n\nINSTRUCTIONS:\n1. Convert LCP_ms to seconds (1 decimal)\n2. Choose RANDOM subject line from options\n3. Choose RANDOM opening compliment\n4. Choose RANDOM transition to the issue\n5. Choose RANDOM call-to-action\n6. Choose RANDOM P.S.\n\nOutput Format (ONLY valid JSON):\n{\n \"subject\": \"[chosen subject line]\",\n \"body\": \"[full email body with \\n\\n between paragraphs]\"\n}\n\nIMPORTANT: \n- Use \\n\\n between paragraphs\n- No markdown\n- ONLY JSON\n- Each email must be UNIQUE",
"hasOutputParser": true,
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 2.2,
"position": [864, 880],
"name": "AI Agent - Optimization",
"retryOnFail": true,
"maxTries": 5
},
{
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini"
},
"options": {
"responseFormat": "json_object"
}
},
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.2,
"position": [864, 1104],
"name": "OpenAI Chat Model",
"credentials": {
"openAiApi": {
"id": "YOUR_CREDENTIAL_ID",
"name": "OpenAi account"
}
}
},
{
"parameters": {
"jsonSchemaExample": "{\n\t\"subject\": \"subject\",\n\t\"body\": \"body\"\n}"
},
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"typeVersion": 1.3,
"position": [1008, 1104],
"name": "Structured Output Parser"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"name": "finalScreenshotBase64",
"value": "={{ $('HTTP Request').item.json.lighthouseResult.audits['final-screenshot'].details.data.split(',')[1] }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [1680, 880],
"name": "Extract Screenshot"
},
{
"parameters": {
"operation": "toBinary",
"sourceProperty": "finalScreenshotBase64",
"options": {}
},
"type": "n8n-nodes-base.convertToFile",
"typeVersion": 1.1,
"position": [1904, 880],
"name": "Convert to File"
},
{
"parameters": {
"subject": "={{ $('Format Output').item.json.subject }}",
"textContent": "={{ $('Format Output').item.json.body }}",
"sender": "your@email.com",
"receipients": "={{ $('Loop Over Items').item.json.property_email }}",
"additionalFields": {
"emailAttachments": {
"attachment": {
"binaryPropertyName": "data"
}
}
}
},
"type": "n8n-nodes-base.sendInBlue",
"typeVersion": 1,
"position": [2112, 880],
"name": "Send Email via Brevo",
"retryOnFail": true,
"credentials": {
"sendInBlueApi": {
"id": "YOUR_CREDENTIAL_ID",
"name": "Brevo account"
}
}
},
{
"parameters": {
"resource": "databasePage",
"operation": "update",
"pageId": {
"__rl": true,
"value": "={{ $('Loop Over Items').item.json.id }}",
"mode": "id"
},
"propertiesUi": {
"propertyValues": [
{
"key": "Emails send|number",
"numberValue": 1
},
{
"key": "Last Email|date",
"includeTime": false,
"date": "={{ new Date().toISOString() }}"
},
{
"key": "Status|status",
"statusValue": "Sended"
},
{
"key": "Score|number",
"numberValue": "={{ $('Score Classification').item.json.score }}"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.notion",
"typeVersion": 2.2,
"position": [2320, 880],
"name": "Update Notion Record",
"credentials": {
"notionApi": {
"id": "YOUR_CREDENTIAL_ID",
"name": "Notion account"
}
}
},
{
"parameters": {
"options": {}
},
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [-656, 880],
"name": "Loop Over Items"
},
{
"parameters": {
"amount": "={{ Math.floor(Math.random() * 41) + 30 }}"
},
"type": "n8n-nodes-base.wait",
"typeVersion": 1.1,
"position": [1728, 1344],
"name": "Random Wait 30-70s"
}
],
"connections": {
"Get many database pages": {
"main": [
[{ "node": "Loop Over Items", "type": "main", "index": 0 }]
]
},
"When clicking 'Execute workflow'": {
"main": [
[
{
"node": "Get many database pages",
"type": "main",
"index": 0
}
]
]
},
"HTTP Request": {
"main": [
[{ "node": "Score Classification", "type": "main", "index": 0 }]
]
},
"Score Classification": {
"main": [
[{ "node": "Fetch Website HTML", "type": "main", "index": 0 }]
]
},
"Fetch Website HTML": {
"main": [
[
{
"node": "Extract Meta Description",
"type": "main",
"index": 0
}
]
]
},
"Extract Meta Description": {
"main": [[{ "node": "Route by Score", "type": "main", "index": 0 }]]
},
"Route by Score": {
"main": [
[{ "node": "AI Agent - Redesign", "type": "main", "index": 0 }],
[
{
"node": "AI Agent - Optimization",
"type": "main",
"index": 0
}
],
[{ "node": "AI Agent - Upsell", "type": "main", "index": 0 }]
]
},
"Loop Over Items": {
"main": [
[],
[{ "node": "HTTP Request", "type": "main", "index": 0 }]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent - Optimization",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "AI Agent - Optimization",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"AI Agent - Optimization": {
"main": [[{ "node": "Format Output", "type": "main", "index": 0 }]]
},
"Format Output": {
"main": [
[{ "node": "Extract Screenshot", "type": "main", "index": 0 }]
]
},
"Extract Screenshot": {
"main": [
[{ "node": "Convert to File", "type": "main", "index": 0 }]
]
},
"Convert to File": {
"main": [
[{ "node": "Send Email via Brevo", "type": "main", "index": 0 }]
]
},
"Send Email via Brevo": {
"main": [
[{ "node": "Update Notion Record", "type": "main", "index": 0 }]
]
},
"Update Notion Record": {
"main": [
[{ "node": "Random Wait 30-70s", "type": "main", "index": 0 }]
]
},
"Random Wait 30-70s": {
"main": [
[{ "node": "Loop Over Items", "type": "main", "index": 0 }]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
json
Setup Instructions
To use this workflow:
- Import the JSON into your N8N instance
- Set up credentials for:
- Notion API (create an integration at notion.so/my-integrations)
- OpenAI API key
- Google PageSpeed Insights API key
- Brevo (SendInBlue) API key
- Create a Notion database with these properties:
Email(email type)Name(title)URL(url)Sector(select)Emails send(number)Last Email(date)Status(status)Score(number)Email Body(rich text)
- Update the workflow with your Notion database ID and sender email
Lessons Learned
- Data-driven personalization works: Using real website metrics makes outreach feel genuine rather than templated
- AI prompt engineering matters: Structured prompts with randomized elements create variety while maintaining quality
- Rate limiting is essential: Proper delays between sends significantly improve deliverability
- Integration flexibility: N8N's node-based approach made it easy to iterate and add features without rebuilding the entire workflow