This workflow follows the OpenAI → Postgres 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 →
{
"name": "AI Relevance Analyzer - Improved",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "webhook/ai-analyzer",
"options": {}
},
"id": "analyzer_trigger",
"name": "Analyzer Trigger - Secured",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
300,
300
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "CREATE INDEX IF NOT EXISTS idx_jobs_processed ON jobs (is_processed, scraped_at DESC); CREATE INDEX IF NOT EXISTS idx_relevance_scores_job_user ON relevance_scores (job_id, user_name); SELECT j.*, rs.score as existing_score FROM jobs j WHERE j.is_processed = false AND NOT EXISTS (SELECT 1 FROM relevance_scores rs WHERE rs.job_id = j.id AND rs.user_name = 'Vitalii') ORDER BY j.scraped_at DESC LIMIT 50;",
"options": {}
},
"id": "get_unprocessed_jobs",
"name": "Get Unprocessed Jobs - Optimized",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
500,
300
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"resource": "chat",
"operation": "message",
"chatModel": "gpt-4o-mini",
"messages": {
"messages": [
{
"role": "system",
"content": "\u0422\u0438 \u0435\u043a\u0441\u043f\u0435\u0440\u0442 HR \u0430\u043d\u0430\u043b\u0456\u0442\u0438\u043a \u0434\u043b\u044f \u043e\u0446\u0456\u043d\u043a\u0438 \u0440\u0435\u043b\u0435\u0432\u0430\u043d\u0442\u043d\u043e\u0441\u0442\u0456 \u0432\u0430\u043a\u0430\u043d\u0441\u0456\u0439. \u0410\u043d\u0430\u043b\u0456\u0437\u0443\u0439 \u0432\u0430\u043a\u0430\u043d\u0441\u0456\u0457 \u0434\u043b\u044f \u0443\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u043e\u0433\u043e \u0440\u043e\u0437\u0440\u043e\u0431\u043d\u0438\u043a\u0430 \u0437 \u0434\u043e\u0441\u0432\u0456\u0434\u043e\u043c Python, JavaScript, \u0432\u0435\u0431-\u0440\u043e\u0437\u0440\u043e\u0431\u043a\u0438.\n\n\u0420\u0415\u0417\u042e\u041c\u0415 \u041a\u041e\u0420\u0418\u0421\u0422\u0423\u0412\u0410\u0427\u0410:\n- \u0414\u043e\u0441\u0432\u0456\u0434: Python, Django, JavaScript, React, Node.js\n- \u0420\u0456\u0432\u0435\u043d\u044c: Middle/Senior Developer\n- \u041c\u043e\u0432\u0438: \u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430 (\u0440\u0456\u0434\u043d\u0430), \u041d\u043e\u0440\u0432\u0435\u0437\u044c\u043a\u0430 (A2), \u0410\u043d\u0433\u043b\u0456\u0439\u0441\u044c\u043a\u0430 (B2)\n- \u041b\u043e\u043a\u0430\u0446\u0456\u044f: \u041e\u0441\u043b\u043e, \u041d\u043e\u0440\u0432\u0435\u0433\u0456\u044f\n- \u0413\u043e\u0442\u043e\u0432\u043d\u0456\u0441\u0442\u044c \u0434\u043e \u0440\u043e\u0431\u043e\u0442\u0438: Full-time, \u0432\u0456\u0434\u0434\u0430\u043b\u0435\u043d\u043e \u0430\u0431\u043e \u043e\u0444\u0456\u0441 \u0432 \u041e\u0441\u043b\u043e\n\n\u041e\u0426\u0406\u041d\u042e\u0419 \u041a\u041e\u0416\u041d\u0423 \u0412\u0410\u041a\u0410\u041d\u0421\u0406\u042e \u0417\u0410 \u041a\u0420\u0418\u0422\u0415\u0420\u0406\u042f\u041c\u0418:\n1. \u0412\u0456\u0434\u043f\u043e\u0432\u0456\u0434\u043d\u0456\u0441\u0442\u044c \u0442\u0435\u0445\u043d\u0456\u0447\u043d\u0438\u0445 \u043d\u0430\u0432\u0438\u0447\u043e\u043a (40%)\n2. \u0420\u0456\u0432\u0435\u043d\u044c \u0434\u043e\u0441\u0432\u0456\u0434\u0443 (30%)\n3. \u041b\u043e\u043a\u0430\u0446\u0456\u044f \u0442\u0430 \u0443\u043c\u043e\u0432\u0438 (20%)\n4. \u041c\u043e\u0432\u043d\u0456 \u0432\u0438\u043c\u043e\u0433\u0438 (10%)\n\n\u0412\u0406\u0414\u041f\u041e\u0412\u0406\u0414\u0410\u0419 JSON:\n{\n \"score\": 85,\n \"reasoning\": \"\u0412\u0438\u0441\u043e\u043a\u0438\u0439 \u0437\u0431\u0456\u0433 \u043d\u0430\u0432\u0438\u0447\u043e\u043a Python/Django, \u043b\u043e\u043a\u0430\u0446\u0456\u044f \u041e\u0441\u043b\u043e \u043f\u0456\u0434\u0445\u043e\u0434\u0438\u0442\u044c...\",\n \"key_matches\": [\"Python\", \"Django\", \"Oslo\"],\n \"missing_requirements\": [\"5+ years experience\"],\n \"recommendation\": \"apply\"\n}"
},
{
"role": "user",
"content": "\u041f\u0440\u043e\u0430\u043d\u0430\u043b\u0456\u0437\u0443\u0439 \u0446\u044e \u0432\u0430\u043a\u0430\u043d\u0441\u0456\u044e:\n\n\u041d\u0430\u0437\u0432\u0430: {{ $json.title }}\n\u041a\u043e\u043c\u043f\u0430\u043d\u0456\u044f: {{ $json.company }}\n\u041b\u043e\u043a\u0430\u0446\u0456\u044f: {{ $json.location }}\n\u0414\u0436\u0435\u0440\u0435\u043b\u043e: {{ $json.source }}\nURL: {{ $json.url }}\n\n\u041e\u0446\u0456\u043d\u0438 \u0440\u0435\u043b\u0435\u0432\u0430\u043d\u0442\u043d\u0456\u0441\u0442\u044c \u0432\u0456\u0434 0 \u0434\u043e 100 \u0442\u0430 \u043f\u043e\u044f\u0441\u043d\u0438 \u0440\u0456\u0448\u0435\u043d\u043d\u044f."
}
]
}
},
"id": "analyze_job_relevance",
"name": "Analyze Job Relevance - Reliable",
"type": "n8n-nodes-base.openAi",
"typeVersion": 1.2,
"position": [
700,
300
],
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"continueOnFail": true,
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 2000
},
{
"parameters": {
"functionCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n try {\n const jobData = item.json;\n let analysis = {};\n \n if (item.error) {\n analysis = {\n score: 25,\n reasoning: 'AI analysis failed: ' + item.error.message,\n key_matches: [],\n missing_requirements: ['AI analysis error'],\n recommendation: 'review_manually'\n };\n } else {\n const aiResponse = item.json.message?.content || item.json.text || '';\n \n try {\n const jsonMatch = aiResponse.match(/\\{[\\s\\S]*\\}/);\n analysis = JSON.parse(jsonMatch ? jsonMatch[0] : aiResponse);\n analysis.score = Math.min(Math.max(analysis.score || 0, 0), 100);\n } catch (parseError) {\n analysis = {\n score: 30,\n reasoning: 'JSON parse failed: ' + parseError.message,\n key_matches: [],\n missing_requirements: ['JSON parsing error'],\n recommendation: 'review_manually'\n };\n }\n }\n \n results.push({\n job_id: jobData.id,\n title: jobData.title,\n company: jobData.company,\n url: jobData.url,\n score: analysis.score,\n reasoning: (analysis.reasoning || 'No reasoning').replace(/'/g, \"''\"),\n key_matches: JSON.stringify(analysis.key_matches || []),\n missing_requirements: JSON.stringify(analysis.missing_requirements || []),\n recommendation: analysis.recommendation || 'review',\n ai_analysis: JSON.stringify(analysis),\n analyzed_at: new Date().toISOString()\n });\n } catch (error) {\n results.push({\n job_id: item.json?.id || 0,\n score: 0,\n reasoning: 'Error: ' + error.message,\n recommendation: 'error'\n });\n }\n}\n\nreturn results;"
},
"id": "process_ai_results",
"name": "Process AI Results - Enhanced",
"type": "n8n-nodes-base.function",
"typeVersion": 1,
"position": [
900,
300
]
},
{
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO relevance_scores (job_id, user_name, score, reasoning, ai_analysis, created_at) VALUES {{ $json.map(item => `(${item.job_id}, 'Vitalii', ${item.score}, '${item.reasoning}', '${item.ai_analysis.replace(/'/g, \"''\")}', '${item.analyzed_at}')`).join(', ') }}; UPDATE jobs SET is_processed = true WHERE id IN ({{ $json.map(item => item.job_id).join(', ') }}); SELECT j.*, rs.score, rs.reasoning, rs.recommendation FROM jobs j JOIN relevance_scores rs ON j.id = rs.job_id WHERE rs.score >= 80 AND rs.user_name = 'Vitalii' AND j.id IN ({{ $json.map(item => item.job_id).join(', ') }}) ORDER BY rs.score DESC;",
"options": {}
},
"id": "save_analysis_results",
"name": "Save Analysis Results - Improved",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.5,
"position": [
1100,
300
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"chatId": "{{ $env.TELEGRAM_CHAT_ID }}",
"text": "\ud83e\udde0 AI Analysis Complete\\n\\n\ud83d\udcca Jobs analyzed: {{ $('Process AI Results - Enhanced').all().length }}\\n\ud83c\udfaf High relevance (80%+): {{ $json.length }}\\n\\n{{ $json.length > 0 ? '\u2705 Starting application process...' : '\u2139\ufe0f No high-relevance jobs found' }}\\n\\n\ud83d\udd52 Completed: {{ $now.format('HH:mm DD/MM/YYYY') }}"
},
"id": "send_notification",
"name": "Send Enhanced Notification",
"type": "n8n-nodes-base.telegram",
"typeVersion": 1.2,
"position": [
1300,
300
],
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
}
}
],
"connections": {
"Analyzer Trigger - Secured": {
"main": [
[
{
"node": "Get Unprocessed Jobs - Optimized",
"type": "main",
"index": 0
}
]
]
},
"Get Unprocessed Jobs - Optimized": {
"main": [
[
{
"node": "Analyze Job Relevance - Reliable",
"type": "main",
"index": 0
}
]
]
},
"Analyze Job Relevance - Reliable": {
"main": [
[
{
"node": "Process AI Results - Enhanced",
"type": "main",
"index": 0
}
]
]
},
"Process AI Results - Enhanced": {
"main": [
[
{
"node": "Save Analysis Results - Improved",
"type": "main",
"index": 0
}
]
]
},
"Save Analysis Results - Improved": {
"main": [
[
{
"node": "Send Enhanced Notification",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": [],
"triggerCount": 0,
"updatedAt": "2025-01-20T12:00:00.000Z",
"versionId": "2"
}
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.
openAiApipostgrestelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
AI Relevance Analyzer - Improved. Uses postgres, openAi, telegram. Webhook trigger; 6 nodes.
Source: https://github.com/SmmShaman/jobbot-norway-public/blob/04db9f51b6c604268bd2be177806f9186ec89e4e/n8n-workflows/02_job_scanner_fixed.json — 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.
Eu Clara – Funil Kiwify Completo. Uses postgres, openAi, httpRequest, gmail. Webhook trigger; 70 nodes.
Lua Nova - Sistema Completo. Uses postgres, httpRequest, openAi. Webhook trigger; 55 nodes.
User Signup & Verification: The workflow starts when a user signs up. It generates a verification code and sends it via SMS using Twilio. Code Validation: The user replies with the code. The workflow
Pyragogy AI Village - Orchestrazione Master (Architettura Profonda V2). Uses start, postgres, openAi, emailSend. Webhook trigger; 36 nodes.
Pyragogy AI Village - Orchestrazione Master (Architettura Profonda V2). Uses start, postgres, openAi, emailSend. Webhook trigger; 35 nodes.