n8n Workflow Management & Pre-population Guide
Status: Complete
Created: 2025-08-02
Framework: n8n API Integration for NudgeCampaign
Executive Summary
This guide provides comprehensive implementation details for programmatically managing n8n workflows in NudgeCampaign, including pre-populating default campaign processes, API management, template deployment strategies, and CRITICAL: Zero-manual-steps automated deployment (V4 Lesson #37).
Table of Contents
- n8n API Overview
- Workflow Management via API
- Default Campaign Templates
- Deployment Scripts
- Integration Implementation
- Testing & Validation
n8n API Overview
API Capabilities
n8n provides a comprehensive REST API for workflow management:
- Workflow CRUD Operations: Create, read, update, delete workflows
- Execution Management: Trigger and monitor workflow executions
- Credential Management: Secure storage of API keys and credentials
- Template Import/Export: JSON-based workflow definitions
- Activation Control: Enable/disable workflows programmatically
Authentication
API Key Generation (Required for API Access)
Generate API Key in n8n UI:
- Open n8n: http://localhost:5678
- Login with admin/password
- Navigate to: Settings β API
- Click "Generate API Key"
- Copy and save the key securely
Working API Configuration:
// n8n API authentication - WORKING EXAMPLE
const n8nConfig = {
baseUrl: process.env.N8N_BASE_URL || 'http://localhost:5678',
apiKey: process.env.N8N_API_KEY, // Required for API access
// Basic auth is NOT sufficient for API operations
};
// Example with actual working setup
const API_KEY = 'your-generated-api-key-here';
const N8N_URL = 'http://localhost:5678';
// Simple working client
async function deployWorkflow(workflow) {
const response = await fetch(`${N8N_URL}/api/v1/workflows`, {
method: 'POST',
headers: {
'X-N8N-API-KEY': API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(workflow)
});
return response.json();
}
Important API Limitations
Note: The n8n API has the following limitations:
- Cannot set
activefield during creation (read-only) - Cannot include
tagsfield during creation (read-only) - Cannot activate workflows via API (requires manual UI toggle)
- Must remove
idfield when creating from template
Workflow Management via API
Core API Client
// src/lib/n8n/n8n-client.ts
import axios, { AxiosInstance } from 'axios';
export interface N8nWorkflow {
id?: string;
name: string;
active: boolean;
nodes: N8nNode[];
connections: N8nConnections;
settings?: N8nSettings;
tags?: string[];
}
export interface N8nNode {
id: string;
name: string;
type: string;
typeVersion: number;
position: [number, number];
parameters: Record<string, any>;
credentials?: Record<string, any>;
}
export interface N8nConnections {
[nodeId: string]: {
main: Array<Array<{ node: string; type: string; index: number }>>
}
}
export class N8nClient {
private client: AxiosInstance;
constructor(config: {
baseUrl: string;
apiKey?: string;
auth?: { username: string; password: string };
}) {
this.client = axios.create({
baseURL: `${config.baseUrl}/api/v1`,
headers: {
'Content-Type': 'application/json',
...(config.apiKey && { 'X-N8N-API-KEY': config.apiKey })
},
...(config.auth && { auth: config.auth })
});
}
// Create a new workflow
async createWorkflow(workflow: N8nWorkflow): Promise<N8nWorkflow> {
const response = await this.client.post('/workflows', workflow);
return response.data;
}
// Get all workflows
async getWorkflows(): Promise<N8nWorkflow[]> {
const response = await this.client.get('/workflows');
return response.data.data;
}
// Get specific workflow
async getWorkflow(id: string): Promise<N8nWorkflow> {
const response = await this.client.get(`/workflows/${id}`);
return response.data;
}
// Update workflow
async updateWorkflow(id: string, workflow: Partial<N8nWorkflow>): Promise<N8nWorkflow> {
const response = await this.client.patch(`/workflows/${id}`, workflow);
return response.data;
}
// Delete workflow
async deleteWorkflow(id: string): Promise<void> {
await this.client.delete(`/workflows/${id}`);
}
// Activate/Deactivate workflow
async activateWorkflow(id: string, active: boolean): Promise<void> {
await this.updateWorkflow(id, { active });
}
// Execute workflow
async executeWorkflow(id: string, data?: any): Promise<any> {
const response = await this.client.post(`/workflows/${id}/run`, { data });
return response.data;
}
// Import workflow from JSON
async importWorkflow(workflowJson: string): Promise<N8nWorkflow> {
const workflow = JSON.parse(workflowJson);
return this.createWorkflow(workflow);
}
// Export workflow to JSON
async exportWorkflow(id: string): Promise<string> {
const workflow = await this.getWorkflow(id);
return JSON.stringify(workflow, null, 2);
}
}
Simplified API Implementation (Recommended)
For a clean, working implementation, we've created an isolated directory with simple API integration:
Directory Structure
n8n-api/ # Isolated, clean directory
βββ deploy.js # Deploy workflows via API
βββ test.js # Test API connection
βββ complete-deploy.js # Full deployment with cleanup
βββ activate.js # Attempt activation (limited by API)
βββ workflows/ # Your workflow JSON files
Working Example
// n8n-api/deploy.js - Simple working deployment
const API_KEY = 'your-api-key-here';
const N8N_URL = 'http://localhost:5678';
async function deployWorkflow() {
// Load workflow (remove read-only fields)
const workflow = JSON.parse(fs.readFileSync('workflow.json'));
delete workflow.id;
delete workflow.active; // Cannot set during creation
delete workflow.tags; // Read-only field
// Deploy via API
const response = await fetch(`${N8N_URL}/api/v1/workflows`, {
method: 'POST',
headers: {
'X-N8N-API-KEY': API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify(workflow)
});
if (response.ok) {
const created = await response.json();
console.log(`β
Created workflow: ${created.id}`);
// Note: Must manually activate in UI
}
}
Quick Start
# Test API connection
node n8n-api/test.js
# Deploy workflows
node n8n-api/deploy.js
# Then activate in UI and test
curl -X POST http://localhost:5678/webhook/your-webhook -d '{"test":true}'
Default Campaign Templates
Template Library
// src/lib/n8n/templates/campaign-templates.ts
export const CAMPAIGN_TEMPLATES = {
// Welcome Series Template
welcomeSeries: {
name: 'Welcome Series - 3 Email Sequence',
description: 'Automated welcome series for new subscribers',
tags: ['welcome', 'onboarding', 'automated'],
workflow: {
name: 'Welcome Series Campaign',
active: false,
nodes: [
{
id: 'webhook_trigger',
name: 'New Subscriber Webhook',
type: 'n8n-nodes-base.webhook',
typeVersion: 1,
position: [250, 300],
parameters: {
path: 'new-subscriber',
responseMode: 'onReceived',
responseData: 'allEntries',
options: {}
}
},
{
id: 'get_subscriber',
name: 'Get Subscriber Details',
type: 'n8n-nodes-base.postgres',
typeVersion: 2,
position: [450, 300],
parameters: {
operation: 'executeQuery',
query: `
SELECT * FROM contacts
WHERE email = '{{ $json["email"] }}'
AND tenant_id = '{{ $json["tenant_id"] }}'
`,
additionalFields: {}
}
},
{
id: 'email_1',
name: 'Welcome Email',
type: 'n8n-nodes-base.emailSend',
typeVersion: 2,
position: [650, 300],
parameters: {
fromEmail: '{{ $json["sender_email"] }}',
toEmail: '{{ $json["email"] }}',
subject: 'Welcome to {{ $json["company_name"] }}!',
emailType: 'html',
htmlBody: `
<h2>Welcome {{ $json["first_name"] }}!</h2>
<p>We're excited to have you on board.</p>
<p>Here's what you can expect from us:</p>
<ul>
<li>Weekly tips and insights</li>
<li>Exclusive offers for subscribers</li>
<li>Early access to new features</li>
</ul>
<p>Best regards,<br>The {{ $json["company_name"] }} Team</p>
`,
options: {
allowUnauthorizedCerts: false
}
}
},
{
id: 'wait_3_days',
name: 'Wait 3 Days',
type: 'n8n-nodes-base.wait',
typeVersion: 1,
position: [850, 300],
parameters: {
amount: 3,
unit: 'days'
}
},
{
id: 'email_2',
name: 'Education Email',
type: 'n8n-nodes-base.emailSend',
typeVersion: 2,
position: [1050, 300],
parameters: {
fromEmail: '{{ $json["sender_email"] }}',
toEmail: '{{ $json["email"] }}',
subject: 'Getting Started with {{ $json["company_name"] }}',
emailType: 'html',
htmlBody: `
<h2>Hi {{ $json["first_name"] }},</h2>
<p>Here are 3 quick tips to get the most out of our service:</p>
<ol>
<li>Complete your profile for personalized recommendations</li>
<li>Connect your tools for seamless integration</li>
<li>Explore our resource library</li>
</ol>
<p>Need help? Just reply to this email!</p>
`,
options: {}
}
},
{
id: 'wait_7_days',
name: 'Wait 7 Days',
type: 'n8n-nodes-base.wait',
typeVersion: 1,
position: [1250, 300],
parameters: {
amount: 7,
unit: 'days'
}
},
{
id: 'email_3',
name: 'Engagement Email',
type: 'n8n-nodes-base.emailSend',
typeVersion: 2,
position: [1450, 300],
parameters: {
fromEmail: '{{ $json["sender_email"] }}',
toEmail: '{{ $json["email"] }}',
subject: 'Your First Week with {{ $json["company_name"] }}',
emailType: 'html',
htmlBody: `
<h2>How's it going, {{ $json["first_name"] }}?</h2>
<p>You've been with us for a week now. How are you finding things?</p>
<p>We'd love to hear your feedback or help with any questions.</p>
<p>As a thank you for being a new subscriber, here's a special offer just for you:</p>
<p><strong>Use code WELCOME20 for 20% off your first purchase!</strong></p>
`,
options: {}
}
},
{
id: 'update_status',
name: 'Update Contact Status',
type: 'n8n-nodes-base.postgres',
typeVersion: 2,
position: [1650, 300],
parameters: {
operation: 'executeQuery',
query: `
UPDATE contacts
SET
campaign_status = 'welcome_completed',
last_email_sent = NOW(),
updated_at = NOW()
WHERE email = '{{ $json["email"] }}'
AND tenant_id = '{{ $json["tenant_id"] }}'
`,
additionalFields: {}
}
}
],
connections: {
'webhook_trigger': {
main: [[{ node: 'get_subscriber', type: 'main', index: 0 }]]
},
'get_subscriber': {
main: [[{ node: 'email_1', type: 'main', index: 0 }]]
},
'email_1': {
main: [[{ node: 'wait_3_days', type: 'main', index: 0 }]]
},
'wait_3_days': {
main: [[{ node: 'email_2', type: 'main', index: 0 }]]
},
'email_2': {
main: [[{ node: 'wait_7_days', type: 'main', index: 0 }]]
},
'wait_7_days': {
main: [[{ node: 'email_3', type: 'main', index: 0 }]]
},
'email_3': {
main: [[{ node: 'update_status', type: 'main', index: 0 }]]
}
},
settings: {
executionOrder: 'v1',
saveDataSuccessExecution: 'all',
saveDataErrorExecution: 'all',
saveExecutionProgress: true,
saveManualExecutions: true,
callerPolicy: 'workflowsFromSameOwner'
}
}
},
// Abandoned Cart Recovery Template
abandonedCart: {
name: 'Abandoned Cart Recovery',
description: 'Recover lost sales with automated cart reminders',
tags: ['ecommerce', 'recovery', 'automated'],
workflow: {
name: 'Abandoned Cart Recovery Campaign',
active: false,
nodes: [
{
id: 'schedule_trigger',
name: 'Check Abandoned Carts',
type: 'n8n-nodes-base.scheduleTrigger',
typeVersion: 1,
position: [250, 300],
parameters: {
rule: {
interval: [{ hours: 1 }]
}
}
},
{
id: 'get_abandoned_carts',
name: 'Get Abandoned Carts',
type: 'n8n-nodes-base.postgres',
typeVersion: 2,
position: [450, 300],
parameters: {
operation: 'executeQuery',
query: `
SELECT
c.email,
c.first_name,
c.tenant_id,
cart.items,
cart.total_value,
cart.created_at
FROM carts cart
JOIN contacts c ON cart.contact_id = c.id
WHERE cart.status = 'abandoned'
AND cart.created_at < NOW() - INTERVAL '2 hours'
AND cart.recovery_email_sent = false
`,
additionalFields: {}
}
},
{
id: 'split_batch',
name: 'Process Each Cart',
type: 'n8n-nodes-base.splitInBatches',
typeVersion: 1,
position: [650, 300],
parameters: {
batchSize: 1,
options: {}
}
},
{
id: 'send_recovery_email',
name: 'Send Recovery Email',
type: 'n8n-nodes-base.emailSend',
typeVersion: 2,
position: [850, 300],
parameters: {
fromEmail: 'shop@example.com',
toEmail: '{{ $json["email"] }}',
subject: '{{ $json["first_name"] }}, you forgot something!',
emailType: 'html',
htmlBody: `
<h2>Hi {{ $json["first_name"] }},</h2>
<p>You left some items in your cart. Complete your purchase now!</p>
<p>Cart value: ${{ $json["total_value"] }}</p>
<p><a href="https://shop.example.com/cart/recover?token={{ $json["recovery_token"] }}">Complete Purchase</a></p>
<p>This cart will expire in 24 hours.</p>
`,
options: {}
}
},
{
id: 'mark_email_sent',
name: 'Mark Email Sent',
type: 'n8n-nodes-base.postgres',
typeVersion: 2,
position: [1050, 300],
parameters: {
operation: 'executeQuery',
query: `
UPDATE carts
SET
recovery_email_sent = true,
recovery_email_sent_at = NOW()
WHERE contact_id = (
SELECT id FROM contacts
WHERE email = '{{ $json["email"] }}'
AND tenant_id = '{{ $json["tenant_id"] }}'
)
`,
additionalFields: {}
}
}
],
connections: {
'schedule_trigger': {
main: [[{ node: 'get_abandoned_carts', type: 'main', index: 0 }]]
},
'get_abandoned_carts': {
main: [[{ node: 'split_batch', type: 'main', index: 0 }]]
},
'split_batch': {
main: [[{ node: 'send_recovery_email', type: 'main', index: 0 }]]
},
'send_recovery_email': {
main: [[{ node: 'mark_email_sent', type: 'main', index: 0 }]]
},
'mark_email_sent': {
main: [[{ node: 'split_batch', type: 'main', index: 0 }]]
}
}
}
},
// Re-engagement Campaign Template
reEngagement: {
name: 'Re-engagement Campaign',
description: 'Win back inactive subscribers',
tags: ['retention', 'win-back', 'automated'],
workflow: {
name: 'Re-engagement Campaign',
active: false,
nodes: [
{
id: 'weekly_trigger',
name: 'Weekly Check',
type: 'n8n-nodes-base.scheduleTrigger',
typeVersion: 1,
position: [250, 300],
parameters: {
rule: {
interval: [{
days: [1], // Monday
hours: 9
}]
}
}
},
{
id: 'get_inactive_users',
name: 'Get Inactive Users',
type: 'n8n-nodes-base.postgres',
typeVersion: 2,
position: [450, 300],
parameters: {
operation: 'executeQuery',
query: `
SELECT
email,
first_name,
last_activity_date,
tenant_id
FROM contacts
WHERE last_activity_date < NOW() - INTERVAL '30 days'
AND status = 'active'
AND re_engagement_sent = false
`,
additionalFields: {}
}
},
{
id: 'personalize_message',
name: 'Personalize Message',
type: 'n8n-nodes-base.code',
typeVersion: 1,
position: [650, 300],
parameters: {
language: 'javascript',
code: `
const items = $input.all();
return items.map(item => {
const daysSinceActivity = Math.floor(
(Date.now() - new Date(item.json.last_activity_date)) / (1000 * 60 * 60 * 24)
);
let subject = '';
let message = '';
if (daysSinceActivity < 45) {
subject = 'We miss you!';
message = 'It\\'s been a while since we\\'ve seen you.';
} else if (daysSinceActivity < 60) {
subject = 'Come back for 20% off';
message = 'Here\\'s a special offer just for you.';
} else {
subject = 'Last chance to stay subscribed';
message = 'We\\'ll remove you from our list soon unless you confirm.';
}
return {
json: {
...item.json,
subject,
message,
daysSinceActivity
}
};
});
`
}
},
{
id: 'send_reengagement',
name: 'Send Re-engagement Email',
type: 'n8n-nodes-base.emailSend',
typeVersion: 2,
position: [850, 300],
parameters: {
fromEmail: 'hello@example.com',
toEmail: '{{ $json["email"] }}',
subject: '{{ $json["subject"] }}',
emailType: 'html',
htmlBody: `
<h2>Hi {{ $json["first_name"] }},</h2>
<p>{{ $json["message"] }}</p>
<p>Want to stay connected?</p>
<p><a href="https://example.com/reconfirm?email={{ $json["email"] }}">Yes, keep me subscribed</a></p>
<p><a href="https://example.com/unsubscribe?email={{ $json["email"] }}">No, unsubscribe me</a></p>
`,
options: {}
}
}
],
connections: {
'weekly_trigger': {
main: [[{ node: 'get_inactive_users', type: 'main', index: 0 }]]
},
'get_inactive_users': {
main: [[{ node: 'personalize_message', type: 'main', index: 0 }]]
},
'personalize_message': {
main: [[{ node: 'send_reengagement', type: 'main', index: 0 }]]
}
}
}
}
};
Deployment Scripts
CRITICAL: Zero-Manual-Steps Deployment (V4 Lesson #37)
Problem: Manual n8n workflow import is error-prone and often forgotten
Solution: Automated import scripts that run during Docker deployment
Result: Zero manual steps - workflows deploy with docker-compose up
Automated Workflow Deployment Structure
# Directory structure for automated deployment
nudgecampaign-mvp-v4/
βββ n8n-workflows/ # Workflow JSON files
β βββ welcome-series.json
β βββ abandoned-cart.json
β βββ re-engagement.json
β βββ lead-nurturing.json
βββ scripts/
β βββ import-n8n-workflows.sh # Auto-import during Docker startup
β βββ import-via-api.sh # Manual fallback script
β βββ import-via-ui.sh # UI-based import helper
βββ docker-compose.yml # Configured for auto-import
Docker Compose Auto-Import Configuration
# docker-compose.yml - Zero manual steps configuration
version: '3.8'
services:
n8n:
image: n8nio/n8n:latest
container_name: nudgecampaign-n8n
ports:
- "5678:5678"
environment:
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=password
- N8N_HOST=localhost
- N8N_PORT=5678
- N8N_PROTOCOL=http
- WEBHOOK_URL=http://localhost:5678
volumes:
- ./n8n-data:/home/node/.n8n
- ./n8n-workflows:/workflows # Mount workflow JSON files
- ./scripts:/scripts # Mount import scripts
# CRITICAL: Auto-import on container start
entrypoint: >
sh -c "
n8n start &
N8N_PID=$!
echo 'Waiting for n8n to start...'
sleep 15
echo 'Importing workflows...'
/scripts/import-n8n-workflows.sh
wait $N8N_PID
"
restart: unless-stopped
networks:
- nudgecampaign-network
Automated Import Script
#!/bin/bash
# scripts/import-n8n-workflows.sh
# CRITICAL: This runs automatically during Docker startup
set -e
echo "π Starting automated n8n workflow import..."
# Configuration
N8N_URL="${N8N_URL:-http://localhost:5678}"
N8N_USER="${N8N_BASIC_AUTH_USER:-admin}"
N8N_PASS="${N8N_BASIC_AUTH_PASSWORD:-password}"
WORKFLOW_DIR="/workflows"
# Wait for n8n to be fully ready
wait_for_n8n() {
local max_attempts=30
local attempt=0
while [ $attempt -lt $max_attempts ]; do
if curl -s -u "$N8N_USER:$N8N_PASS" "$N8N_URL/api/v1/workflows" > /dev/null 2>&1; then
echo "β
n8n is ready!"
return 0
fi
echo "β³ Waiting for n8n API... (attempt $((attempt+1))/$max_attempts)"
sleep 2
attempt=$((attempt+1))
done
echo "β n8n failed to start within timeout"
return 1
}
# Import a single workflow
import_workflow() {
local workflow_file="$1"
local workflow_name=$(basename "$workflow_file" .json)
echo "π Importing workflow: $workflow_name"
# Read and clean the workflow JSON
workflow_json=$(cat "$workflow_file" | jq 'del(.id) | .active = false')
# Check if workflow already exists
existing=$(curl -s -u "$N8N_USER:$N8N_PASS" \
"$N8N_URL/api/v1/workflows" | \
jq -r ".data[] | select(.name == \"$workflow_name\") | .id")
if [ -n "$existing" ]; then
echo " β οΈ Workflow already exists (ID: $existing), skipping..."
return 0
fi
# Import the workflow
response=$(curl -s -X POST \
-u "$N8N_USER:$N8N_PASS" \
-H "Content-Type: application/json" \
-d "$workflow_json" \
"$N8N_URL/api/v1/workflows")
workflow_id=$(echo "$response" | jq -r '.id')
if [ "$workflow_id" != "null" ] && [ -n "$workflow_id" ]; then
echo " β
Imported successfully (ID: $workflow_id)"
# Note: Cannot activate via API due to n8n limitations
echo " βΉοΈ Please activate manually in n8n UI"
else
echo " β Import failed: $(echo "$response" | jq -r '.message')"
return 1
fi
}
# Main import process
main() {
# Wait for n8n to be ready
if ! wait_for_n8n; then
echo "β Aborting workflow import - n8n not ready"
exit 1
fi
# Import all workflows
if [ -d "$WORKFLOW_DIR" ]; then
echo "π Found workflow directory: $WORKFLOW_DIR"
for workflow_file in "$WORKFLOW_DIR"/*.json; do
if [ -f "$workflow_file" ]; then
import_workflow "$workflow_file" || true
fi
done
echo "β
Workflow import complete!"
else
echo "β οΈ No workflow directory found at $WORKFLOW_DIR"
fi
}
# Run the import
main
Manual Fallback Import Script
#!/bin/bash
# scripts/import-via-api.sh
# Manual fallback if automatic import fails
echo "π§ Manual n8n Workflow Import Tool"
echo "=================================="
# Get API key if not using basic auth
read -p "Enter n8n API key (or press Enter for basic auth): " API_KEY
if [ -z "$API_KEY" ]; then
read -p "Enter n8n username (default: admin): " N8N_USER
N8N_USER=${N8N_USER:-admin}
read -sp "Enter n8n password: " N8N_PASS
echo
AUTH_HEADER="Authorization: Basic $(echo -n "$N8N_USER:$N8N_PASS" | base64)"
else
AUTH_HEADER="X-N8N-API-KEY: $API_KEY"
fi
# Import workflows
for file in n8n-workflows/*.json; do
echo "Importing $(basename $file)..."
curl -X POST http://localhost:5678/api/v1/workflows \
-H "$AUTH_HEADER" \
-H "Content-Type: application/json" \
-d @"$file"
echo
done
echo "β
Manual import complete!"
Workflow JSON Template
{
"name": "Welcome Series Campaign",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "new-subscriber",
"responseMode": "onReceived",
"responseData": "{ \"success\": true }"
},
"id": "webhook_trigger",
"name": "New Subscriber Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [250, 300]
},
{
"parameters": {
"amount": 5,
"unit": "minutes"
},
"id": "wait_node",
"name": "Wait 5 Minutes",
"type": "n8n-nodes-base.wait",
"typeVersion": 1,
"position": [450, 300]
},
{
"parameters": {
"server": "={{ $credentials.serverToken }}",
"fromEmail": "{{ $json.sender_email }}",
"toEmail": "{{ $json.email }}",
"subject": "Welcome to {{ $json.company_name }}!",
"textBody": "Hi {{ $json.first_name }},\n\nWelcome aboard!",
"htmlBody": "<h1>Welcome!</h1><p>Hi {{ $json.first_name }},</p>"
},
"id": "send_email",
"name": "Send Welcome Email",
"type": "n8n-nodes-base.postmark",
"typeVersion": 1,
"position": [650, 300]
}
],
"connections": {
"webhook_trigger": {
"main": [[{ "node": "wait_node", "type": "main", "index": 0 }]]
},
"wait_node": {
"main": [[{ "node": "send_email", "type": "main", "index": 0 }]]
}
},
"active": false,
"settings": {
"saveDataSuccessExecution": "all",
"saveExecutionProgress": true,
"saveManualExecutions": true
}
}
Integration Implementation
NextJS API Integration
// src/app/api/n8n/workflows/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { N8nClient } from '@/lib/n8n/n8n-client';
import { auth } from '@/lib/auth';
const client = new N8nClient({
baseUrl: process.env.N8N_BASE_URL!,
auth: {
username: process.env.N8N_BASIC_AUTH_USER!,
password: process.env.N8N_BASIC_AUTH_PASSWORD!
}
});
// GET /api/n8n/workflows - List all workflows
export async function GET(request: NextRequest) {
const session = await auth();
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const workflows = await client.getWorkflows();
return NextResponse.json(workflows);
} catch (error) {
console.error('Failed to fetch workflows:', error);
return NextResponse.json({ error: 'Failed to fetch workflows' }, { status: 500 });
}
}
// POST /api/n8n/workflows - Create new workflow
export async function POST(request: NextRequest) {
const session = await auth();
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const workflow = await request.json();
const created = await client.createWorkflow(workflow);
return NextResponse.json(created);
} catch (error) {
console.error('Failed to create workflow:', error);
return NextResponse.json({ error: 'Failed to create workflow' }, { status: 500 });
}
}
Workflow Execution Trigger
// src/lib/n8n/workflow-trigger.ts
import { N8nClient } from './n8n-client';
export class WorkflowTrigger {
private client: N8nClient;
constructor() {
this.client = new N8nClient({
baseUrl: process.env.N8N_BASE_URL!,
auth: {
username: process.env.N8N_BASIC_AUTH_USER!,
password: process.env.N8N_BASIC_AUTH_PASSWORD!
}
});
}
// Trigger welcome series for new subscriber
async triggerWelcomeSeries(subscriber: {
email: string;
firstName: string;
tenantId: string;
}) {
const webhookUrl = `${process.env.N8N_BASE_URL}/webhook/new-subscriber`;
const response = await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: subscriber.email,
first_name: subscriber.firstName,
tenant_id: subscriber.tenantId,
sender_email: 'hello@example.com',
company_name: 'Your Company',
timestamp: new Date().toISOString()
})
});
if (!response.ok) {
throw new Error(`Failed to trigger welcome series: ${response.statusText}`);
}
return response.json();
}
// Trigger abandoned cart recovery
async triggerAbandonedCartRecovery(cartId: string) {
// The abandoned cart workflow runs on a schedule,
// but we can manually trigger it for immediate processing
const workflows = await this.client.getWorkflows();
const abandonedCartWorkflow = workflows.find(w =>
w.name === 'Abandoned Cart Recovery Campaign'
);
if (!abandonedCartWorkflow) {
throw new Error('Abandoned cart workflow not found');
}
return this.client.executeWorkflow(abandonedCartWorkflow.id!, {
cartId,
triggerType: 'manual'
});
}
}
React Hook for Workflow Management
// src/hooks/use-n8n-workflows.ts
import { useState, useEffect } from 'react';
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then(res => res.json());
export function useN8nWorkflows() {
const { data, error, mutate } = useSWR('/api/n8n/workflows', fetcher);
const createWorkflow = async (workflow: any) => {
const response = await fetch('/api/n8n/workflows', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(workflow)
});
if (!response.ok) {
throw new Error('Failed to create workflow');
}
const created = await response.json();
mutate(); // Refresh the list
return created;
};
const activateWorkflow = async (id: string, active: boolean) => {
const response = await fetch(`/api/n8n/workflows/${id}/activate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ active })
});
if (!response.ok) {
throw new Error('Failed to update workflow');
}
mutate(); // Refresh the list
};
return {
workflows: data || [],
isLoading: !error && !data,
isError: error,
createWorkflow,
activateWorkflow
};
}
Testing & Validation
Integration Tests
// tests/n8n-integration.test.ts
import { N8nClient } from '../src/lib/n8n/n8n-client';
import { CAMPAIGN_TEMPLATES } from '../src/lib/n8n/templates/campaign-templates';
describe('n8n Workflow Integration', () => {
let client: N8nClient;
beforeAll(() => {
client = new N8nClient({
baseUrl: process.env.N8N_BASE_URL || 'http://localhost:5678',
auth: {
username: 'admin',
password: 'password'
}
});
});
test('should connect to n8n instance', async () => {
const workflows = await client.getWorkflows();
expect(Array.isArray(workflows)).toBe(true);
});
test('should create welcome series workflow', async () => {
const template = CAMPAIGN_TEMPLATES.welcomeSeries;
const created = await client.createWorkflow(template.workflow);
expect(created).toHaveProperty('id');
expect(created.name).toBe(template.workflow.name);
expect(created.nodes).toHaveLength(template.workflow.nodes.length);
});
test('should execute workflow with test data', async () => {
const workflows = await client.getWorkflows();
const welcomeWorkflow = workflows.find(w =>
w.name === 'Welcome Series Campaign'
);
if (welcomeWorkflow) {
const result = await client.executeWorkflow(welcomeWorkflow.id!, {
email: 'test@example.com',
first_name: 'Test',
tenant_id: 'test-tenant',
sender_email: 'hello@example.com',
company_name: 'Test Company'
});
expect(result).toHaveProperty('executionId');
}
});
});
Validation Script
// scripts/validate-n8n-setup.ts
async function validateN8nSetup() {
console.log('π Validating n8n setup...\n');
const checks = [
{
name: 'n8n accessibility',
test: async () => {
const response = await fetch('http://localhost:5678/healthz');
return response.ok;
}
},
{
name: 'API authentication',
test: async () => {
const response = await fetch('http://localhost:5678/api/v1/workflows', {
headers: {
'Authorization': 'Basic ' + Buffer.from('admin:password').toString('base64')
}
});
return response.ok;
}
},
{
name: 'Database connection',
test: async () => {
// Check if n8n can connect to PostgreSQL
const client = new N8nClient({
baseUrl: 'http://localhost:5678',
auth: { username: 'admin', password: 'password' }
});
const workflows = await client.getWorkflows();
return true;
}
},
{
name: 'Webhook accessibility',
test: async () => {
const response = await fetch('http://localhost:5678/webhook-test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ test: true })
});
// Webhook might not exist, but we're checking if the endpoint is reachable
return response.status !== 502;
}
}
];
for (const check of checks) {
try {
const passed = await check.test();
console.log(passed ? `β
${check.name}` : `β ${check.name}`);
} catch (error) {
console.log(`β ${check.name}: ${error.message}`);
}
}
console.log('\nβ
Validation complete!');
}
validateN8nSetup().catch(console.error);
Usage Examples
Creating a Custom Campaign Workflow
// Example: Creating a custom product launch campaign
const customWorkflow = {
name: 'Product Launch Campaign',
active: false,
nodes: [
// Define your custom nodes here
],
connections: {
// Define connections between nodes
},
tags: ['custom', 'product-launch']
};
const client = new N8nClient(config);
const created = await client.createWorkflow(customWorkflow);
console.log(`Created workflow: ${created.id}`);
Triggering Workflows from Application Events
// In your application code
import { WorkflowTrigger } from '@/lib/n8n/workflow-trigger';
const trigger = new WorkflowTrigger();
// When a user signs up
export async function handleUserSignup(user: User) {
// Save to database
await saveUser(user);
// Trigger welcome series
await trigger.triggerWelcomeSeries({
email: user.email,
firstName: user.firstName,
tenantId: user.tenantId
});
}
Best Practices
- Version Control: Store workflow definitions in Git for version control
- Environment Separation: Use different n8n instances for dev/staging/production
- Error Handling: Implement proper error handling in all workflows
- Monitoring: Set up alerts for failed workflow executions
- Testing: Test workflows with sample data before activating
- Documentation: Document each workflow's purpose and trigger conditions
- Security: Use environment variables for sensitive data
- Backup: Regularly export and backup workflow definitions
Monitoring & Maintenance
Workflow Health Dashboard
// src/components/n8n-dashboard.tsx
export function N8nDashboard() {
const { workflows } = useN8nWorkflows();
return (
<div className="grid grid-cols-3 gap-4">
<div className="stat">
<div className="stat-title">Total Workflows</div>
<div className="stat-value">{workflows.length}</div>
</div>
<div className="stat">
<div className="stat-title">Active Workflows</div>
<div className="stat-value">
{workflows.filter(w => w.active).length}
</div>
</div>
<div className="stat">
<div className="stat-title">Templates Available</div>
<div className="stat-value">
{Object.keys(CAMPAIGN_TEMPLATES).length}
</div>
</div>
</div>
);
}
Conclusion
This guide provides a complete framework for managing n8n workflows programmatically in NudgeCampaign. By following these patterns, you can:
- Pre-populate n8n with default campaign workflows
- Manage workflows via API
- Integrate workflow triggers with your application
- Monitor and maintain workflow health
- Scale your automation capabilities
The combination of n8n's visual workflow builder with programmatic management creates a powerful, flexible automation platform for email marketing campaigns.