API Integration Implementation: Building the Email Marketing Backend
Phase: 14 - MVP Implementation with Claude Code
Status: Executable API Development Guide
Research Foundation: Next.js API routes, Supabase integration, and email service patterns
Executive Summary
Connect frontend to backend with production-ready APIs. This guide provides executable Claude Code prompts to build comprehensive API endpoints, database integrations, and third-party service connections. Transform the UI components into a fully functional email marketing platform.
API-First Implementation Strategy
This guide contains 6 major Claude Code execution prompts that will create:
- RESTful API Routes for campaigns, contacts, templates, and automation management
- AI Workflow Generation APIs for natural language to n8n workflow conversion
- n8n Integration APIs for workflow deployment, monitoring, and execution
- Conversational AI APIs for Maya assistant and voice processing
- Supabase Database Integration with real-time subscriptions and complex queries
- Email Service Integration with SendGrid/Postmark for reliable email delivery
- Real-Time Features with websockets, notifications, and collaborative editing
Key Innovation: Production-Ready API Architecture
| Traditional Backend | Claude Code Implementation | Advantage |
|---|---|---|
| Weeks of API development | 4 prompts = complete backend | 15x faster API development |
| Manual database queries | Type-safe Supabase integration | 95% fewer SQL errors |
| Basic email sending | Professional delivery infrastructure | Enterprise-grade reliability |
| No real-time features | Built-in real-time subscriptions | Modern user experience |
RESTful API Routes Implementation
Campaign Management API
Comprehensive API endpoints for managing email campaigns with full CRUD operations, status management, and analytics integration.
Claude Code Prompt #1: Campaign API Endpoints
Execution Priority: FIRST - Core campaign management functionality
Prompt for Claude Code:
Create comprehensive Next.js API routes for email marketing campaign management with the following specifications:
CAMPAIGN API ENDPOINTS:
Create src/app/api/campaigns/ with:
1. route.ts (GET /api/campaigns, POST /api/campaigns):
```typescript
import { NextRequest, NextResponse } from 'next/server';
import { createServerSupabaseClient } from '@/lib/supabase/server';
import { campaignSchema, CampaignCreateData } from '@/lib/validation/campaign';
export async function GET(request: NextRequest) {
try {
const supabase = createServerSupabaseClient();
const { data: { user }, error: authError } = await supabase.auth.getUser();
if (authError || !user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const url = new URL(request.url);
const page = parseInt(url.searchParams.get('page') || '1');
const limit = parseInt(url.searchParams.get('limit') || '10');
const status = url.searchParams.get('status');
const search = url.searchParams.get('search');
let query = supabase
.from('campaigns')
.select(`
id, name, subject, status, recipient_count, sent_count,
open_count, click_count, created_at, updated_at, scheduled_at,
open_rate, click_rate
`)
.eq('user_id', user.id)
.order('created_at', { ascending: false });
if (status) {
query = query.eq('status', status);
}
if (search) {
query = query.or(`name.ilike.%${search}%,subject.ilike.%${search}%`);
}
const offset = (page - 1) * limit;
query = query.range(offset, offset + limit - 1);
const { data: campaigns, error, count } = await query;
if (error) {
throw error;
}
return NextResponse.json({
campaigns,
pagination: {
page,
limit,
total: count || 0,
pages: Math.ceil((count || 0) / limit)
}
});
} catch (error) {
console.error('Campaign fetch error:', error);
return NextResponse.json(
{ error: 'Failed to fetch campaigns' },
{ status: 500 }
);
}
}
export async function POST(request: NextRequest) {
try {
const supabase = createServerSupabaseClient();
const { data: { user }, error: authError } = await supabase.auth.getUser();
if (authError || !user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const body = await request.json();
const validatedData = campaignSchema.parse(body);
const { data: campaign, error } = await supabase
.from('campaigns')
.insert({
user_id: user.id,
name: validatedData.name,
subject: validatedData.subject,
content: validatedData.content,
status: 'draft'
})
.select()
.single();
if (error) {
throw error;
}
return NextResponse.json({ campaign }, { status: 201 });
} catch (error) {
console.error('Campaign creation error:', error);
return NextResponse.json(
{ error: 'Failed to create campaign' },
{ status: 500 }
);
}
}
- [id]/route.ts (GET, PUT, DELETE /api/campaigns/[id]):
- Individual campaign operations
- Status updates (draft, scheduled, sending, sent, paused)
- Analytics calculation and caching
- Campaign duplication functionality
- [id]/send/route.ts (POST /api/campaigns/[id]/send):
- Campaign sending logic with queue integration
- Recipient validation and segmentation
- Send scheduling and rate limiting
- Delivery tracking setup
- [id]/analytics/route.ts (GET /api/campaigns/[id]/analytics):
- Detailed campaign performance metrics
- Open/click tracking data
- Geographic and device analytics
- Time-based performance trends
CONTACT API ENDPOINTS:
Create src/app/api/contacts/ with:
- route.ts (GET /api/contacts, POST /api/contacts):
- Contact listing with advanced filtering
- Bulk contact creation
- Duplicate detection and handling
- Tag-based querying
- [id]/route.ts (GET, PUT, DELETE /api/contacts/[id]):
- Individual contact management
- Custom field updates
- Engagement history retrieval
- Contact merge functionality
- import/route.ts (POST /api/contacts/import):
- CSV file processing
- Field mapping and validation
- Batch import with progress tracking
- Error reporting and resolution
- segments/route.ts (GET, POST /api/contacts/segments):
- Dynamic segmentation logic
- Segment creation and management
- Real-time segment statistics
- Export segment functionality
TEMPLATE API ENDPOINTS:
Create src/app/api/templates/ with:
- route.ts (GET /api/templates, POST /api/templates):
- Template library management
- Category-based organization
- Template sharing and permissions
- Usage analytics tracking
- [id]/route.ts (GET, PUT, DELETE /api/templates/[id]):
- Individual template operations
- Version history management
- Template preview generation
- Collaborative editing support
AUTOMATION API ENDPOINTS:
Create src/app/api/automations/ with:
- route.ts (GET /api/automations, POST /api/automations):
- Automation workflow management
- Trigger configuration validation
- Workflow status management
- Performance analytics
- [id]/route.ts (GET, PUT, DELETE /api/automations/[id]):
- Individual workflow operations
- Node configuration updates
- Execution history retrieval
- Workflow testing functionality
- [id]/execute/route.ts (POST /api/automations/[id]/execute):
- Manual workflow execution
- Test mode execution
- Execution logging and monitoring
- Error handling and recovery
DATA VALIDATION:
Create src/lib/validation/ with Zod schemas:
- campaignSchema for campaign creation/updates
- contactSchema for contact operations
- templateSchema for template management
- automationSchema for workflow validation
ERROR HANDLING:
Implement comprehensive error handling:
- Request validation with detailed error messages
- Database error handling with user-friendly responses
- Rate limiting and quota management
- Logging and monitoring integration
SECURITY FEATURES:
- Row Level Security (RLS) enforcement
- Input sanitization and validation
- Rate limiting per user/endpoint
- API key authentication for webhooks
- CORS configuration for frontend access
Include comprehensive request/response typing, error handling, and performance optimization.
**Expected Output**: Complete RESTful API with all email marketing operations
---
## π€ AI Workflow Generation API
### π― Revolutionary Natural Language to Automation Conversion
**Critical Innovation**: Transform natural language conversations into complete n8n workflows using AI, eliminating the need for traditional drag-and-drop builders.
### π€ Claude Code Prompt #2: AI Workflow Generation API
**Execution Priority**: SECOND - Core AI functionality
**Prompt for Claude Code**:
Create comprehensive AI workflow generation API endpoints that convert natural language to n8n workflows:
API ENDPOINTS:
Create src/app/api/ai/ with:
- workflow-generate/route.ts (POST /api/ai/workflow-generate):
import { NextRequest, NextResponse } from 'next/server';
import { OpenAI } from 'openai';
import { createServerSupabaseClient } from '@/lib/supabase/server';
import { generateN8nWorkflow } from '@/lib/ai/workflow-generator';
import { deployWorkflowToN8n } from '@/lib/n8n/client';
export async function POST(request: NextRequest) {
try {
const { userMessage, businessContext, existingWorkflows } = await request.json();
// Authenticate user
const supabase = createServerSupabaseClient();
const { data: { user }, error: authError } = await supabase.auth.getUser();
if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
// Generate workflow using AI
const workflowSpec = await generateN8nWorkflow({
userIntent: userMessage,
businessContext,
userHistory: existingWorkflows,
userId: user.id
});
// Convert to n8n JSON format
const n8nWorkflow = await convertToN8nFormat(workflowSpec);
// Store in database
const { data: automation } = await supabase
.from('automations')
.insert({
user_id: user.id,
name: workflowSpec.name,
description: workflowSpec.description,
n8n_workflow_json: n8nWorkflow,
ai_generated: true,
ai_conversation_context: { userMessage, businessContext },
status: 'draft'
})
.select()
.single();
return NextResponse.json({
automation,
workflowPreview: workflowSpec,
estimatedSavings: calculateTimeSavings(workflowSpec)
});
} catch (error) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
- conversation/route.ts (POST /api/ai/conversation):
- Maya AI assistant conversation handling
- Context-aware responses about email marketing
- Intent recognition and workflow suggestions
- Voice input processing and natural responses
- voice-process/route.ts (POST /api/ai/voice-process):
- Google Cloud Speech-to-Text integration
- Text-to-Speech for Maya responses
- Real-time voice streaming support
- Voice command recognition for automation
- workflow-optimize/route.ts (POST /api/ai/workflow-optimize):
- AI-powered workflow performance analysis
- Optimization suggestions based on execution data
- A/B testing recommendations
- Continuous improvement suggestions
Include complete TypeScript types, error handling, and performance monitoring.
**Expected Output**: Complete AI workflow generation infrastructure
---
## π§ n8n Integration API
### π― Seamless n8n Cloud Run Integration
**Integration Foundation**: Connect Next.js application to self-hosted n8n instance for workflow deployment and execution monitoring.
### π€ Claude Code Prompt #3: n8n API Integration
**Execution Priority**: THIRD - After AI workflow generation
**Prompt for Claude Code**:
Create comprehensive n8n API integration for workflow management and execution:
N8N CLIENT LIBRARY:
Create src/lib/n8n/client.ts:
import axios from 'axios';
import { N8nWorkflow, WorkflowExecution, N8nNode } from '@/types/n8n';
export class N8nClient {
private baseUrl: string;
private apiKey: string;
constructor() {
this.baseUrl = process.env.N8N_API_URL!;
this.apiKey = process.env.N8N_API_KEY!;
}
// Workflow management
async createWorkflow(workflow: N8nWorkflow): Promise<string> {
const response = await axios.post(`${this.baseUrl}/api/v1/workflows`, workflow, {
headers: { 'X-N8N-API-KEY': this.apiKey }
});
return response.data.id;
}
async updateWorkflow(id: string, workflow: N8nWorkflow): Promise<void> {
await axios.put(`${this.baseUrl}/api/v1/workflows/${id}`, workflow, {
headers: { 'X-N8N-API-KEY': this.apiKey }
});
}
async deleteWorkflow(id: string): Promise<void> {
await axios.delete(`${this.baseUrl}/api/v1/workflows/${id}`, {
headers: { 'X-N8N-API-KEY': this.apiKey }
});
}
// Workflow execution
async executeWorkflow(id: string, data?: any): Promise<WorkflowExecution> {
const response = await axios.post(`${this.baseUrl}/api/v1/workflows/${id}/execute`,
{ data },
{ headers: { 'X-N8N-API-KEY': this.apiKey } }
);
return response.data;
}
async getExecutionStatus(executionId: string): Promise<WorkflowExecution> {
const response = await axios.get(`${this.baseUrl}/api/v1/executions/${executionId}`, {
headers: { 'X-N8N-API-KEY': this.apiKey }
});
return response.data;
}
// Real-time monitoring
async getActiveExecutions(): Promise<WorkflowExecution[]> {
const response = await axios.get(`${this.baseUrl}/api/v1/executions/active`, {
headers: { 'X-N8N-API-KEY': this.apiKey }
});
return response.data;
}
}
API ENDPOINTS:
Create src/app/api/n8n/ with:
- workflows/route.ts (GET, POST /api/n8n/workflows):
- Deploy AI-generated workflows to n8n
- Retrieve workflow status and configuration
- Update workflow settings and activation
- execute/route.ts (POST /api/n8n/execute):
- Trigger workflow executions
- Pass dynamic data to workflows
- Handle webhook-based executions
- monitor/route.ts (GET /api/n8n/monitor):
- Real-time execution monitoring
- Performance metrics collection
- Error tracking and alerting
- webhooks/route.ts (POST /api/n8n/webhooks):
- Handle n8n webhook callbacks
- Process execution results
- Update database with execution status
Include comprehensive error handling, retry logic, and monitoring.
**Expected Output**: Complete n8n integration with monitoring and execution
---
## ποΈ Supabase Database Integration
### π― Real-Time Database Operations
Advanced Supabase integration with real-time subscriptions, complex queries, and optimized data relationships for the email marketing platform.
### π€ Claude Code Prompt #4: Database Integration Implementation
**Execution Priority**: SECOND - After API routes are established
**Prompt for Claude Code**:
Implement comprehensive Supabase database integration with the following specifications:
DATABASE CLIENT SETUP:
Create src/lib/supabase/ with:
- client.ts (Browser client):
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs';
import { Database } from '@/types/database';
export const createClient = () => createBrowserSupabaseClient<Database>();
- server.ts (Server client):
import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { Database } from '@/types/database';
export const createServerSupabaseClient = () =>
createServerSupabaseClient<Database>({ cookies });
- middleware.ts (Middleware client):
import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export async function middleware(req: NextRequest) {
const res = NextResponse.next();
const supabase = createMiddlewareSupabaseClient({ req, res });
await supabase.auth.getSession();
return res;
}
DATABASE OPERATIONS:
Create src/lib/database/ with:
- campaigns.ts:
import { createClient } from '@/lib/supabase/client';
import { Campaign, CampaignAnalytics, CampaignFilters } from '@/types/campaign';
export class CampaignDatabase {
private supabase = createClient();
async getCampaigns(filters: CampaignFilters = {}) {
let query = this.supabase
.from('campaigns')
.select(`
id, name, subject, status, recipient_count, sent_count,
open_count, click_count, created_at, updated_at, scheduled_at,
open_rate, click_rate
`);
if (filters.status) {
query = query.eq('status', filters.status);
}
if (filters.search) {
query = query.or(`name.ilike.%${filters.search}%,subject.ilike.%${filters.search}%`);
}
if (filters.dateFrom) {
query = query.gte('created_at', filters.dateFrom);
}
if (filters.dateTo) {
query = query.lte('created_at', filters.dateTo);
}
const { data, error } = await query
.order('created_at', { ascending: false })
.range(filters.offset || 0, (filters.offset || 0) + (filters.limit || 10) - 1);
if (error) throw error;
return data as Campaign[];
}
async getCampaignAnalytics(campaignId: string): Promise<CampaignAnalytics> {
const { data, error } = await this.supabase
.rpc('get_campaign_analytics', { campaign_id: campaignId });
if (error) throw error;
return data;
}
async updateCampaignStatus(campaignId: string, status: Campaign['status']) {
const { data, error } = await this.supabase
.from('campaigns')
.update({
status,
updated_at: new Date().toISOString()
})
.eq('id', campaignId)
.select()
.single();
if (error) throw error;
return data;
}
subscribeToCampaignUpdates(callback: (payload: any) => void) {
return this.supabase
.channel('campaign-updates')
.on('postgres_changes',
{
event: '*',
schema: 'public',
table: 'campaigns'
},
callback
)
.subscribe();
}
}
- contacts.ts:
import { createClient } from '@/lib/supabase/client';
import { Contact, ContactFilters, ImportJob } from '@/types/contact';
export class ContactDatabase {
private supabase = createClient();
async getContacts(filters: ContactFilters = {}) {
let query = this.supabase
.from('contacts')
.select('*');
if (filters.tags && filters.tags.length > 0) {
query = query.overlaps('tags', filters.tags);
}
if (filters.status) {
query = query.eq('status', filters.status);
}
if (filters.search) {
query = query.or(`
email.ilike.%${filters.search}%,
first_name.ilike.%${filters.search}%,
last_name.ilike.%${filters.search}%
`);
}
const { data, error } = await query
.order('created_at', { ascending: false })
.range(filters.offset || 0, (filters.offset || 0) + (filters.limit || 50) - 1);
if (error) throw error;
return data as Contact[];
}
async bulkCreateContacts(contacts: Partial<Contact>[]) {
const { data, error } = await this.supabase
.from('contacts')
.insert(contacts)
.select();
if (error) throw error;
return data;
}
async createSegment(name: string, rules: any[]) {
const { data, error } = await this.supabase
.rpc('create_dynamic_segment', {
segment_name: name,
segment_rules: rules
});
if (error) throw error;
return data;
}
async getSegmentContacts(segmentId: string) {
const { data, error } = await this.supabase
.rpc('get_segment_contacts', { segment_id: segmentId });
if (error) throw error;
return data;
}
}
- real-time.ts:
import { createClient } from '@/lib/supabase/client';
import { RealtimeChannel } from '@supabase/supabase-js';
export class RealTimeManager {
private supabase = createClient();
private channels: Map<string, RealtimeChannel> = new Map();
subscribeToTable(
table: string,
event: 'INSERT' | 'UPDATE' | 'DELETE' | '*',
callback: (payload: any) => void,
filter?: string
) {
const channelName = `${table}-${event}-${Date.now()}`;
let channel = this.supabase.channel(channelName);
const config: any = {
event,
schema: 'public',
table
};
if (filter) {
config.filter = filter;
}
channel = channel.on('postgres_changes', config, callback);
channel.subscribe((status) => {
console.log(`Subscription to ${table} ${event}: ${status}`);
});
this.channels.set(channelName, channel);
return channelName;
}
unsubscribe(channelName: string) {
const channel = this.channels.get(channelName);
if (channel) {
this.supabase.removeChannel(channel);
this.channels.delete(channelName);
}
}
unsubscribeAll() {
this.channels.forEach((channel) => {
this.supabase.removeChannel(channel);
});
this.channels.clear();
}
}
DATABASE FUNCTIONS:
Create PostgreSQL functions for complex operations:
- Campaign Analytics Function:
CREATE OR REPLACE FUNCTION get_campaign_analytics(campaign_id UUID)
RETURNS JSON AS $
DECLARE
result JSON;
BEGIN
SELECT json_build_object(
'total_sent', COALESCE(c.sent_count, 0),
'total_opens', COALESCE(c.open_count, 0),
'total_clicks', COALESCE(c.click_count, 0),
'open_rate', CASE
WHEN c.sent_count > 0 THEN (c.open_count::float / c.sent_count) * 100
ELSE 0
END,
'click_rate', CASE
WHEN c.sent_count > 0 THEN (c.click_count::float / c.sent_count) * 100
ELSE 0
END,
'bounce_rate', (
SELECT COUNT(*)::float / GREATEST(c.sent_count, 1) * 100
FROM email_events e
WHERE e.campaign_id = c.id AND e.event_type = 'bounced'
),
'unsubscribe_rate', (
SELECT COUNT(*)::float / GREATEST(c.sent_count, 1) * 100
FROM email_events e
WHERE e.campaign_id = c.id AND e.event_type = 'unsubscribed'
),
'engagement_timeline', (
SELECT json_agg(
json_build_object(
'date', DATE(created_at),
'opens', SUM(CASE WHEN event_type = 'opened' THEN 1 ELSE 0 END),
'clicks', SUM(CASE WHEN event_type = 'clicked' THEN 1 ELSE 0 END)
)
)
FROM email_events
WHERE campaign_id = c.id
GROUP BY DATE(created_at)
ORDER BY DATE(created_at)
)
) INTO result
FROM campaigns c
WHERE c.id = campaign_id;
RETURN result;
END;
$ LANGUAGE plpgsql;
- Dynamic Segmentation Function:
CREATE OR REPLACE FUNCTION create_dynamic_segment(
segment_name TEXT,
segment_rules JSON
)
RETURNS UUID AS $
DECLARE
segment_id UUID;
contact_count INTEGER;
BEGIN
-- Insert segment
INSERT INTO contact_segments (name, rules)
VALUES (segment_name, segment_rules)
RETURNING id INTO segment_id;
-- Calculate contact count
SELECT COUNT(*) INTO contact_count
FROM contacts
WHERE evaluate_segment_rules(id, segment_rules);
-- Update segment with contact count
UPDATE contact_segments
SET contact_count = contact_count
WHERE id = segment_id;
RETURN segment_id;
END;
$ LANGUAGE plpgsql;
PERFORMANCE OPTIMIZATION:
- Database indexes for frequently queried columns
- Query optimization with EXPLAIN ANALYZE
- Connection pooling configuration
- Prepared statement usage
- Result caching for expensive operations
REAL-TIME SUBSCRIPTIONS:
- Campaign status updates
- Contact list changes
- Email event tracking
- Automation execution monitoring
- Collaborative editing synchronization
Include comprehensive error handling, connection management, and performance monitoring.
**Expected Output**: Complete database integration with real-time capabilities and optimized queries
---
## π§ Email Service Integration
### π― Professional Email Delivery Infrastructure
Integration with SendGrid and Postmark for reliable email delivery, template rendering, tracking, and delivery optimization.
### π€ Claude Code Prompt #5: Email Service Implementation
**Execution Priority**: THIRD - After database integration is complete
**Prompt for Claude Code**:
Implement comprehensive email service integration with the following specifications:
EMAIL SERVICE SETUP:
Create src/lib/email/ with:
- sendgrid.ts:
import sgMail from '@sendgrid/mail';
import { EmailTemplate, EmailRecipient, EmailResult } from '@/types/email';
sgMail.setApiKey(process.env.SENDGRID_API_KEY!);
export class SendGridService {
async sendEmail(
template: EmailTemplate,
recipient: EmailRecipient,
trackingData?: Record<string, any>
): Promise<EmailResult> {
try {
const personalizedContent = this.personalizeContent(template.content, recipient);
const msg = {
to: recipient.email,
from: {
email: process.env.FROM_EMAIL!,
name: process.env.FROM_NAME || 'NudgeCampaign'
},
subject: this.personalizeContent(template.subject, recipient),
html: this.addTrackingPixels(personalizedContent, {
campaignId: template.campaignId,
contactId: recipient.id,
...trackingData
}),
customArgs: {
campaign_id: template.campaignId,
contact_id: recipient.id,
user_id: recipient.userId
},
trackingSettings: {
clickTracking: { enable: true },
openTracking: { enable: true },
subscriptionTracking: { enable: false }
}
};
const [response] = await sgMail.send(msg);
return {
success: true,
messageId: response.headers['x-message-id'] as string,
recipient: recipient.email,
timestamp: new Date().toISOString()
};
} catch (error) {
console.error('SendGrid error:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
recipient: recipient.email,
timestamp: new Date().toISOString()
};
}
}
async sendBulkEmails(
template: EmailTemplate,
recipients: EmailRecipient[]
): Promise<EmailResult[]> {
const results: EmailResult[] = [];
const batchSize = 100; // SendGrid recommendation
for (let i = 0; i < recipients.length; i += batchSize) {
const batch = recipients.slice(i, i + batchSize);
const batchPromises = batch.map(recipient =>
this.sendEmail(template, recipient)
);
const batchResults = await Promise.allSettled(batchPromises);
batchResults.forEach((result, index) => {
if (result.status === 'fulfilled') {
results.push(result.value);
} else {
results.push({
success: false,
error: result.reason?.message || 'Unknown error',
recipient: batch[index].email,
timestamp: new Date().toISOString()
});
}
});
// Rate limiting: wait 1 second between batches
if (i + batchSize < recipients.length) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
return results;
}
private personalizeContent(content: string, recipient: EmailRecipient): string {
let personalized = content;
// Replace common personalization tokens
personalized = personalized.replace(/{{first_name}}/g, recipient.firstName || '');
personalized = personalized.replace(/{{last_name}}/g, recipient.lastName || '');
personalized = personalized.replace(/{{email}}/g, recipient.email);
// Replace custom field tokens
if (recipient.customFields) {
Object.entries(recipient.customFields).forEach(([key, value]) => {
const regex = new RegExp(`{{${key}}}`, 'g');
personalized = personalized.replace(regex, String(value));
});
}
return personalized;
}
private addTrackingPixels(html: string, trackingData: Record<string, any>): string {
const trackingPixel = `<img src="${process.env.NEXT_PUBLIC_BASE_URL}/api/email/track/open?${
new URLSearchParams(trackingData).toString()
}" width="1" height="1" style="display:none;" alt="" />`;
// Add tracking pixel before closing body tag
return html.replace('</body>', `${trackingPixel}</body>`);
}
}
- postmark.ts:
import { ServerClient } from 'postmark';
import { EmailTemplate, EmailRecipient, EmailResult } from '@/types/email';
export class PostmarkService {
private client: ServerClient;
constructor() {
this.client = new ServerClient(process.env.POSTMARK_API_KEY!);
}
async sendEmail(
template: EmailTemplate,
recipient: EmailRecipient,
trackingData?: Record<string, any>
): Promise<EmailResult> {
try {
const personalizedContent = this.personalizeContent(template.content, recipient);
const result = await this.client.sendEmail({
From: `${process.env.FROM_NAME} <${process.env.FROM_EMAIL}>`,
To: recipient.email,
Subject: this.personalizeContent(template.subject, recipient),
HtmlBody: this.addTrackingPixels(personalizedContent, {
campaignId: template.campaignId,
contactId: recipient.id,
...trackingData
}),
MessageStream: 'outbound',
Metadata: {
campaign_id: template.campaignId,
contact_id: recipient.id,
user_id: recipient.userId
},
TrackOpens: true,
TrackLinks: 'HtmlOnly'
});
return {
success: true,
messageId: result.MessageID,
recipient: recipient.email,
timestamp: new Date().toISOString()
};
} catch (error) {
console.error('Postmark error:', error);
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
recipient: recipient.email,
timestamp: new Date().toISOString()
};
}
}
async sendBulkEmails(
template: EmailTemplate,
recipients: EmailRecipient[]
): Promise<EmailResult[]> {
const results: EmailResult[] = [];
const batchSize = 500; // Postmark batch limit
for (let i = 0; i < recipients.length; i += batchSize) {
const batch = recipients.slice(i, i + batchSize);
const emails = batch.map(recipient => ({
From: `${process.env.FROM_NAME} <${process.env.FROM_EMAIL}>`,
To: recipient.email,
Subject: this.personalizeContent(template.subject, recipient),
HtmlBody: this.addTrackingPixels(
this.personalizeContent(template.content, recipient),
{
campaignId: template.campaignId,
contactId: recipient.id
}
),
MessageStream: 'outbound',
Metadata: {
campaign_id: template.campaignId,
contact_id: recipient.id,
user_id: recipient.userId
},
TrackOpens: true,
TrackLinks: 'HtmlOnly'
}));
try {
const batchResults = await this.client.sendEmailBatch(emails);
batchResults.forEach((result, index) => {
results.push({
success: result.ErrorCode === 0,
messageId: result.MessageID,
error: result.ErrorCode !== 0 ? result.Message : undefined,
recipient: batch[index].email,
timestamp: new Date().toISOString()
});
});
} catch (error) {
// Handle batch failure
batch.forEach(recipient => {
results.push({
success: false,
error: error instanceof Error ? error.message : 'Batch send failed',
recipient: recipient.email,
timestamp: new Date().toISOString()
});
});
}
}
return results;
}
private personalizeContent(content: string, recipient: EmailRecipient): string {
// Same personalization logic as SendGrid
let personalized = content;
personalized = personalized.replace(/{{first_name}}/g, recipient.firstName || '');
personalized = personalized.replace(/{{last_name}}/g, recipient.lastName || '');
personalized = personalized.replace(/{{email}}/g, recipient.email);
if (recipient.customFields) {
Object.entries(recipient.customFields).forEach(([key, value]) => {
const regex = new RegExp(`{{${key}}}`, 'g');
personalized = personalized.replace(regex, String(value));
});
}
return personalized;
}
private addTrackingPixels(html: string, trackingData: Record<string, any>): string {
const trackingPixel = `<img src="${process.env.NEXT_PUBLIC_BASE_URL}/api/email/track/open?${
new URLSearchParams(trackingData).toString()
}" width="1" height="1" style="display:none;" alt="" />`;
return html.replace('</body>', `${trackingPixel}</body>`);
}
}
EMAIL QUEUE SYSTEM:
Create src/lib/email/queue.ts:
import { createClient } from '@/lib/supabase/client';
import { EmailService } from './service';
export interface EmailJob {
id: string;
campaign_id: string;
recipient_emails: string[];
template: any;
priority: 'low' | 'normal' | 'high';
scheduled_at?: string;
status: 'pending' | 'processing' | 'completed' | 'failed';
created_at: string;
}
export class EmailQueue {
private supabase = createClient();
private emailService = new EmailService();
private isProcessing = false;
async addJob(job: Omit<EmailJob, 'id' | 'status' | 'created_at'>): Promise<string> {
const { data, error } = await this.supabase
.from('email_jobs')
.insert({
...job,
status: 'pending',
created_at: new Date().toISOString()
})
.select('id')
.single();
if (error) throw error;
return data.id;
}
async processQueue(): Promise<void> {
if (this.isProcessing) return;
this.isProcessing = true;
try {
const { data: jobs, error } = await this.supabase
.from('email_jobs')
.select('*')
.eq('status', 'pending')
.order('priority', { ascending: false })
.order('created_at', { ascending: true })
.limit(10);
if (error) throw error;
for (const job of jobs || []) {
await this.processJob(job);
}
} finally {
this.isProcessing = false;
}
}
private async processJob(job: EmailJob): Promise<void> {
try {
// Update job status to processing
await this.supabase
.from('email_jobs')
.update({ status: 'processing' })
.eq('id', job.id);
// Send emails
const results = await this.emailService.sendBulkEmails(
job.template,
job.recipient_emails
);
// Update job status and results
await this.supabase
.from('email_jobs')
.update({
status: 'completed',
results: results,
completed_at: new Date().toISOString()
})
.eq('id', job.id);
// Log individual email events
for (const result of results) {
await this.supabase
.from('email_events')
.insert({
campaign_id: job.campaign_id,
contact_email: result.recipient,
event_type: result.success ? 'sent' : 'failed',
metadata: {
message_id: result.messageId,
error: result.error
}
});
}
} catch (error) {
console.error('Job processing error:', error);
await this.supabase
.from('email_jobs')
.update({
status: 'failed',
error: error instanceof Error ? error.message : 'Unknown error',
completed_at: new Date().toISOString()
})
.eq('id', job.id);
}
}
}
EMAIL TRACKING:
Create src/app/api/email/track/ with:
- open/route.ts (Email open tracking):
import { NextRequest, NextResponse } from 'next/server';
import { createServerSupabaseClient } from '@/lib/supabase/server';
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const campaignId = searchParams.get('campaignId');
const contactId = searchParams.get('contactId');
if (campaignId && contactId) {
try {
const supabase = createServerSupabaseClient();
// Log open event
await supabase.from('email_events').insert({
campaign_id: campaignId,
contact_id: contactId,
event_type: 'opened',
metadata: {
user_agent: request.headers.get('user-agent'),
ip_address: request.ip,
timestamp: new Date().toISOString()
}
});
// Update campaign open count
await supabase.rpc('increment_campaign_opens', {
campaign_id: campaignId
});
} catch (error) {
console.error('Open tracking error:', error);
}
}
// Return 1x1 transparent pixel
const pixel = Buffer.from(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
'base64'
);
return new NextResponse(pixel, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0'
}
});
}
- click/route.ts (Link click tracking):
- URL redirection with click logging
- UTM parameter tracking
- A/B testing link variants
- Click heatmap data collection
WEBHOOK HANDLING:
Create src/app/api/webhooks/ for handling email service webhooks:
- Delivery confirmations
- Bounce handling
- Spam complaints
- Unsubscribe requests
Include comprehensive error handling, retry logic, and delivery optimization.
**Expected Output**: Complete email service integration with delivery tracking and queue processing
---
## β‘ Real-Time Features Implementation
### π― Live Updates and Collaborative Features
Implementation of real-time notifications, live dashboard updates, collaborative editing, and instant user feedback across the platform.
### π€ Claude Code Prompt #6: Real-Time Features Development
**Execution Priority**: FOURTH - Final integration of real-time capabilities
**Prompt for Claude Code**:
Implement comprehensive real-time features with the following specifications:
REAL-TIME INFRASTRUCTURE:
Create src/lib/real-time/ with:
- notifications.ts:
import { createClient } from '@/lib/supabase/client';
import { toast } from 'react-hot-toast';
export interface Notification {
id: string;
user_id: string;
type: 'campaign_sent' | 'automation_triggered' | 'contact_imported' | 'system_alert';
title: string;
message: string;
data?: Record<string, any>;
read: boolean;
created_at: string;
}
export class NotificationManager {
private supabase = createClient();
private subscription: any;
startListening(userId: string) {
this.subscription = this.supabase
.channel(`notifications:${userId}`)
.on('postgres_changes',
{
event: 'INSERT',
schema: 'public',
table: 'notifications',
filter: `user_id=eq.${userId}`
},
(payload) => {
this.handleNewNotification(payload.new as Notification);
}
)
.subscribe();
}
stopListening() {
if (this.subscription) {
this.supabase.removeChannel(this.subscription);
}
}
private handleNewNotification(notification: Notification) {
// Show toast notification
toast.success(notification.title, {
duration: 5000,
onClick: () => this.handleNotificationClick(notification)
});
// Dispatch custom event for notification bell update
window.dispatchEvent(new CustomEvent('new-notification', {
detail: notification
}));
}
private handleNotificationClick(notification: Notification) {
// Mark as read
this.markAsRead(notification.id);
// Navigate based on notification type
switch (notification.type) {
case 'campaign_sent':
window.location.href = `/campaigns/${notification.data?.campaign_id}`;
break;
case 'automation_triggered':
window.location.href = `/automations/${notification.data?.automation_id}`;
break;
case 'contact_imported':
window.location.href = '/contacts';
break;
}
}
async markAsRead(notificationId: string) {
await this.supabase
.from('notifications')
.update({ read: true })
.eq('id', notificationId);
}
async getUnreadCount(userId: string): Promise<number> {
const { count, error } = await this.supabase
.from('notifications')
.select('*', { count: 'exact', head: true })
.eq('user_id', userId)
.eq('read', false);
if (error) throw error;
return count || 0;
}
}
- live-dashboard.ts:
import { createClient } from '@/lib/supabase/client';
import { Campaign, DashboardMetrics } from '@/types/dashboard';
export class LiveDashboard {
private supabase = createClient();
private metricsSubscription: any;
private campaignSubscription: any;
subscribeToMetricsUpdates(
userId: string,
onUpdate: (metrics: Partial<DashboardMetrics>) => void
) {
// Subscribe to campaign changes that affect metrics
this.metricsSubscription = this.supabase
.channel('dashboard-metrics')
.on('postgres_changes',
{
event: '*',
schema: 'public',
table: 'campaigns',
filter: `user_id=eq.${userId}`
},
() => {
this.refreshMetrics(userId, onUpdate);
}
)
.on('postgres_changes',
{
event: '*',
schema: 'public',
table: 'email_events'
},
() => {
this.refreshMetrics(userId, onUpdate);
}
)
.subscribe();
}
subscribeToCampaignUpdates(
userId: string,
onUpdate: (campaigns: Campaign[]) => void
) {
this.campaignSubscription = this.supabase
.channel('campaign-updates')
.on('postgres_changes',
{
event: '*',
schema: 'public',
table: 'campaigns',
filter: `user_id=eq.${userId}`
},
async () => {
const { data: campaigns } = await this.supabase
.from('campaigns')
.select('*')
.eq('user_id', userId)
.order('created_at', { ascending: false })
.limit(10);
if (campaigns) {
onUpdate(campaigns);
}
}
)
.subscribe();
}
private async refreshMetrics(
userId: string,
onUpdate: (metrics: Partial<DashboardMetrics>) => void
) {
try {
const { data: metrics } = await this.supabase
.rpc('get_dashboard_metrics', { user_id: userId });
if (metrics) {
onUpdate(metrics);
}
} catch (error) {
console.error('Metrics refresh error:', error);
}
}
unsubscribe() {
if (this.metricsSubscription) {
this.supabase.removeChannel(this.metricsSubscription);
}
if (this.campaignSubscription) {
this.supabase.removeChannel(this.campaignSubscription);
}
}
}
- collaborative-editing.ts:
import { createClient } from '@/lib/supabase/client';
import { debounce } from 'lodash';
export interface EditingSession {
id: string;
user_id: string;
user_name: string;
document_id: string;
document_type: 'campaign' | 'template' | 'automation';
cursor_position?: number;
selection_start?: number;
selection_end?: number;
last_activity: string;
}
export class CollaborativeEditor {
private supabase = createClient();
private sessionId: string;
private documentId: string;
private documentType: string;
private subscription: any;
private activeUsers: Map<string, EditingSession> = new Map();
constructor(documentId: string, documentType: string) {
this.documentId = documentId;
this.documentType = documentType;
this.sessionId = `${documentId}-${Date.now()}`;
}
async startSession(userId: string, userName: string) {
// Create editing session
await this.supabase
.from('editing_sessions')
.insert({
id: this.sessionId,
user_id: userId,
user_name: userName,
document_id: this.documentId,
document_type: this.documentType,
last_activity: new Date().toISOString()
});
// Subscribe to other users' activities
this.subscription = this.supabase
.channel(`editing:${this.documentId}`)
.on('postgres_changes',
{
event: '*',
schema: 'public',
table: 'editing_sessions',
filter: `document_id=eq.${this.documentId}`
},
(payload) => {
this.handleSessionUpdate(payload);
}
)
.subscribe();
// Start heartbeat to maintain session
this.startHeartbeat();
}
updateCursorPosition = debounce(async (position: number) => {
await this.supabase
.from('editing_sessions')
.update({
cursor_position: position,
last_activity: new Date().toISOString()
})
.eq('id', this.sessionId);
}, 100);
updateSelection = debounce(async (start: number, end: number) => {
await this.supabase
.from('editing_sessions')
.update({
selection_start: start,
selection_end: end,
last_activity: new Date().toISOString()
})
.eq('id', this.sessionId);
}, 100);
private handleSessionUpdate(payload: any) {
const session = payload.new as EditingSession;
if (session.id !== this.sessionId) {
if (payload.eventType === 'INSERT' || payload.eventType === 'UPDATE') {
this.activeUsers.set(session.user_id, session);
this.renderUserCursor(session);
} else if (payload.eventType === 'DELETE') {
this.activeUsers.delete(session.user_id);
this.removeUserCursor(session.user_id);
}
}
}
private renderUserCursor(session: EditingSession) {
// Emit event for UI to render user cursor
window.dispatchEvent(new CustomEvent('user-cursor-update', {
detail: {
userId: session.user_id,
userName: session.user_name,
position: session.cursor_position,
selectionStart: session.selection_start,
selectionEnd: session.selection_end
}
}));
}
private removeUserCursor(userId: string) {
window.dispatchEvent(new CustomEvent('user-cursor-remove', {
detail: { userId }
}));
}
private startHeartbeat() {
setInterval(async () => {
await this.supabase
.from('editing_sessions')
.update({ last_activity: new Date().toISOString() })
.eq('id', this.sessionId);
}, 30000); // Update every 30 seconds
}
async endSession() {
await this.supabase
.from('editing_sessions')
.delete()
.eq('id', this.sessionId);
if (this.subscription) {
this.supabase.removeChannel(this.subscription);
}
}
}
REAL-TIME COMPONENTS:
Create src/components/real-time/ with:
- NotificationBell.tsx:
import { useState, useEffect } from 'react';
import { BellIcon } from '@heroicons/react/24/outline';
import { NotificationManager } from '@/lib/real-time/notifications';
export function NotificationBell({ userId }: { userId: string }) {
const [unreadCount, setUnreadCount] = useState(0);
const [notifications, setNotifications] = useState<Notification[]>([]);
const [manager] = useState(() => new NotificationManager());
useEffect(() => {
manager.startListening(userId);
loadUnreadCount();
const handleNewNotification = (event: CustomEvent) => {
setUnreadCount(prev => prev + 1);
setNotifications(prev => [event.detail, ...prev]);
};
window.addEventListener('new-notification', handleNewNotification as EventListener);
return () => {
manager.stopListening();
window.removeEventListener('new-notification', handleNewNotification as EventListener);
};
}, [userId]);
const loadUnreadCount = async () => {
try {
const count = await manager.getUnreadCount(userId);
setUnreadCount(count);
} catch (error) {
console.error('Error loading unread count:', error);
}
};
return (
<div className="relative">
<button className="p-2 text-gray-500 hover:text-gray-700">
<BellIcon className="h-6 w-6" />
{unreadCount > 0 && (
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center">
{unreadCount > 99 ? '99+' : unreadCount}
</span>
)}
</button>
</div>
);
}
- LiveMetrics.tsx:
import { useState, useEffect } from 'react';
import { LiveDashboard } from '@/lib/real-time/live-dashboard';
import { DashboardMetrics } from '@/types/dashboard';
export function LiveMetrics({ userId }: { userId: string }) {
const [metrics, setMetrics] = useState<DashboardMetrics | null>(null);
const [liveDashboard] = useState(() => new LiveDashboard());
useEffect(() => {
liveDashboard.subscribeToMetricsUpdates(userId, (updatedMetrics) => {
setMetrics(prev => prev ? { ...prev, ...updatedMetrics } : null);
});
return () => {
liveDashboard.unsubscribe();
};
}, [userId]);
if (!metrics) return null;
return (
<div className="grid grid-cols-4 gap-4">
<MetricCard
title="Total Campaigns"
value={metrics.total_campaigns}
change={metrics.campaigns_change_percent}
/>
<MetricCard
title="Total Contacts"
value={metrics.total_contacts}
change={metrics.contacts_change_percent}
/>
<MetricCard
title="Emails Sent"
value={metrics.total_emails_sent}
change={metrics.emails_change_percent}
/>
<MetricCard
title="Average Open Rate"
value={`${metrics.average_open_rate.toFixed(1)}%`}
change={metrics.open_rate_change_percent}
/>
</div>
);
}
WEBSOCKET INTEGRATION:
Create real-time WebSocket connections for:
- Campaign send progress updates
- Live email event tracking
- Collaborative editing cursors
- System status notifications
- Performance monitoring alerts
PERFORMANCE OPTIMIZATION:
- Connection pooling for Supabase subscriptions
- Debounced real-time updates
- Event batching for high-frequency updates
- Memory leak prevention
- Graceful connection handling
Include comprehensive error handling, connection recovery, and performance monitoring.
**Expected Output**: Complete real-time system with live updates, notifications, and collaborative features
---
## π― API Integration Success Validation
### Comprehensive Testing & Verification
After implementing all API integrations, verify functionality with these validation steps:
#### π API Endpoint Testing
- [ ] **Campaign API**: Create, read, update, delete campaigns successfully
- [ ] **Contact API**: Import, export, segment contacts with bulk operations
- [ ] **Template API**: Save, load, share email templates
- [ ] **Automation API**: Create, execute, monitor workflow automations
#### ποΈ Database Integration Verification
- [ ] **Real-Time Subscriptions**: Live updates working across all components
- [ ] **Complex Queries**: Advanced filtering and analytics queries performing well
- [ ] **Data Relationships**: Proper foreign key relationships and data integrity
- [ ] **Performance**: Query optimization and indexing effective
#### π§ Email Service Validation
- [ ] **Email Delivery**: Test emails sending successfully via SendGrid/Postmark
- [ ] **Tracking**: Open and click tracking working correctly
- [ ] **Personalization**: Dynamic content and personalization tokens working
- [ ] **Queue Processing**: Bulk email sending with proper rate limiting
#### β‘ Real-Time Features Testing
- [ ] **Live Dashboard**: Metrics updating in real-time
- [ ] **Notifications**: Push notifications working across browser tabs
- [ ] **Collaborative Editing**: Multiple users can edit simultaneously
- [ ] **Performance**: Real-time features not causing memory leaks
### π Production Readiness Checklist
#### Error Handling & Resilience
- [ ] **API Error Responses**: Proper HTTP status codes and error messages
- [ ] **Database Connection Handling**: Graceful connection recovery
- [ ] **Email Service Fallbacks**: Retry logic and alternative providers
- [ ] **Real-Time Connection Recovery**: Automatic reconnection on failures
#### Security Implementation
- [ ] **Authentication Enforcement**: All APIs require proper authentication
- [ ] **Input Validation**: All user inputs validated and sanitized
- [ ] **Rate Limiting**: API endpoints protected against abuse
- [ ] **Data Encryption**: Sensitive data encrypted in transit and at rest
#### Performance Optimization
- [ ] **API Response Times**: All endpoints responding under 200ms
- [ ] **Database Query Performance**: Complex queries optimized with indexes
- [ ] **Email Sending Speed**: Bulk emails processing efficiently
- [ ] **Real-Time Performance**: Live updates not impacting app performance
This API integration implementation creates a production-ready backend that powers the email marketing platform with reliable data management, email delivery, and real-time user experiences. The system is designed to scale from MVP to enterprise-level usage.