Last updated: Aug 4, 2025, 11:26 AM UTC

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

  1. n8n API Overview
  2. Workflow Management via API
  3. Default Campaign Templates
  4. Deployment Scripts
  5. Integration Implementation
  6. 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)

  1. 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
  2. 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 active field during creation (read-only)
  • Cannot include tags field during creation (read-only)
  • Cannot activate workflows via API (requires manual UI toggle)
  • Must remove id field 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

  1. Version Control: Store workflow definitions in Git for version control
  2. Environment Separation: Use different n8n instances for dev/staging/production
  3. Error Handling: Implement proper error handling in all workflows
  4. Monitoring: Set up alerts for failed workflow executions
  5. Testing: Test workflows with sample data before activating
  6. Documentation: Document each workflow's purpose and trigger conditions
  7. Security: Use environment variables for sensitive data
  8. 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.