Overview
This example demonstrates how to build a sophisticated email campaign system using Conductor’s Email member type with versioned templates from Edgit. We’ll cover:- Creating and versioning email templates
- A/B testing template variations
- Personalizing emails with AI-generated content
- Tracking campaign performance
- Rolling out template updates progressively
Use Case
A SaaS company wants to improve their onboarding email campaign by:- Testing different email designs
- Personalizing content based on user segment
- Measuring which version drives more activations
- Rolling out winners gradually without code deployments
Project Structure
Copy
email-campaign/
├── templates/
│ └── email/
│ ├── welcome-v1.html # Original design
│ ├── welcome-v2.html # Redesign with clearer CTA
│ └── welcome-personalized.html # AI-personalized version
├── prompts/
│ └── personalize-email.md # AI prompt for personalization
├── ensemble.yaml # Campaign workflow
└── wrangler.toml # Cloudflare Workers config
Step 1: Create Email Templates
Template v1.0.0 (Original)
Copy
<!-- templates/email/welcome-v1.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Welcome to {{appName}}</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
}
.button {
display: inline-block;
padding: 12px 24px;
background-color: #007bff;
color: white;
text-decoration: none;
border-radius: 4px;
margin: 20px 0;
}
.footer {
text-align: center;
color: #666;
font-size: 12px;
margin-top: 30px;
}
</style>
</head>
<body>
<div class="container">
<h1>Welcome to {{appName}}!</h1>
<p>Hi {{userName}},</p>
<p>Thanks for signing up. We're excited to have you on board.</p>
<p>Get started by clicking the button below:</p>
<a href="{{activationUrl}}" class="button">Activate Your Account</a>
<p>If you have any questions, feel free to reach out to our support team.</p>
</div>
<div class="footer">
<p>© 2024 {{companyName}}. All rights reserved.</p>
<p><a href="{{unsubscribeUrl}}">Unsubscribe</a></p>
</div>
</body>
</html>
Template v2.0.0 (Improved CTA)
Copy
<!-- templates/email/welcome-v2.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Welcome to {{appName}}</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
background-color: #f8f9fa;
}
.container {
background: white;
padding: 40px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.header {
text-align: center;
margin-bottom: 30px;
}
.logo {
width: 120px;
height: auto;
}
h1 {
color: #1a1a1a;
font-size: 28px;
margin-bottom: 10px;
}
.highlight {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 30px;
border-radius: 8px;
text-align: center;
margin: 20px 0;
}
.button {
display: inline-block;
padding: 16px 32px;
background-color: #28a745;
color: white;
text-decoration: none;
border-radius: 6px;
font-weight: bold;
font-size: 16px;
margin: 10px 0;
}
.button:hover {
background-color: #218838;
}
.benefits {
margin: 30px 0;
}
.benefit {
padding: 15px;
margin: 10px 0;
background: #f8f9fa;
border-left: 4px solid #667eea;
}
.footer {
text-align: center;
color: #666;
font-size: 12px;
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid #e0e0e0;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎉 Welcome, {{userName}}!</h1>
<p>You're about to experience something amazing</p>
</div>
<div class="highlight">
<h2>Your account is ready!</h2>
<p>Click below to activate and start exploring</p>
<a href="{{activationUrl}}" class="button">Get Started Now →</a>
</div>
<div class="benefits">
<h3>What you'll get:</h3>
<div class="benefit">
<strong>✨ Instant Setup</strong><br>
Your workspace is ready to go in seconds
</div>
<div class="benefit">
<strong>🚀 Fast Onboarding</strong><br>
Follow our 3-minute tutorial to master the basics
</div>
<div class="benefit">
<strong>💬 24/7 Support</strong><br>
Our team is here to help whenever you need
</div>
</div>
<p>Questions? Reply to this email anytime.</p>
</div>
<div class="footer">
<p>© 2024 {{companyName}}. All rights reserved.</p>
<p><a href="{{unsubscribeUrl}}" style="color: #666;">Unsubscribe</a></p>
</div>
</body>
</html>
Template v3.0.0 (AI-Personalized)
Copy
<!-- templates/email/welcome-personalized.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{subject}}</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
background-color: #f8f9fa;
}
.container {
background: white;
padding: 40px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.personalized {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 25px;
border-radius: 8px;
margin: 20px 0;
}
.use-cases {
margin: 30px 0;
}
.use-case {
padding: 15px;
margin: 10px 0;
background: #f0f4ff;
border-left: 4px solid #667eea;
border-radius: 4px;
}
.button {
display: inline-block;
padding: 16px 32px;
background-color: #28a745;
color: white;
text-decoration: none;
border-radius: 6px;
font-weight: bold;
font-size: 16px;
margin: 15px 0;
}
.footer {
text-align: center;
color: #666;
font-size: 12px;
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid #e0e0e0;
}
</style>
</head>
<body>
<div class="container">
<h1>{{greeting}}, {{userName}}! 👋</h1>
<div class="personalized">
<p style="font-size: 18px; margin: 0;">{{personalizedMessage}}</p>
</div>
<p>{{introText}}</p>
<div class="use-cases">
<h3>{{useCaseTitle}}</h3>
{{#each useCases}}
<div class="use-case">
<strong>{{this.title}}</strong><br>
{{this.description}}
</div>
{{/each}}
</div>
<div style="text-align: center; margin: 30px 0;">
<a href="{{activationUrl}}" class="button">{{ctaText}} →</a>
</div>
<p>{{closingText}}</p>
</div>
<div class="footer">
<p>© 2024 {{companyName}}. All rights reserved.</p>
<p><a href="{{unsubscribeUrl}}" style="color: #666;">Unsubscribe</a></p>
</div>
</body>
</html>
Step 2: Register Templates in Edgit
Copy
# Register templates
edgit components add welcome-v1 templates/email/welcome-v1.html template \
--metadata '{
"description": "Original welcome email design",
"category": "onboarding",
"variables": ["appName", "userName", "activationUrl", "companyName", "unsubscribeUrl"],
"usedBy": ["welcome-campaign"]
}'
edgit components add welcome-v2 templates/email/welcome-v2.html template \
--metadata '{
"description": "Improved welcome email with enhanced CTA",
"category": "onboarding",
"variables": ["appName", "userName", "activationUrl", "companyName", "unsubscribeUrl"],
"usedBy": ["welcome-campaign"]
}'
edgit components add welcome-personalized templates/email/welcome-personalized.html template \
--metadata '{
"description": "AI-personalized welcome email based on user segment",
"category": "onboarding",
"variables": ["subject", "greeting", "userName", "personalizedMessage", "introText", "useCaseTitle", "useCases", "activationUrl", "ctaText", "closingText", "companyName", "unsubscribeUrl"],
"usedBy": ["welcome-campaign"],
"requiresAI": true
}'
# Version templates
edgit tag create welcome-v1 v1.0.0
edgit tag create welcome-v2 v2.0.0
edgit tag create welcome-personalized v3.0.0
# Deploy to production
edgit deploy set welcome-v1 v1.0.0 --to production
edgit deploy set welcome-v2 v2.0.0 --to staging # Test in staging first
edgit deploy set welcome-personalized v3.0.0 --to canary # Limited rollout
Step 3: Create Personalization Prompt
Copy
<!-- prompts/personalize-email.md -->
You are an email personalization expert. Generate welcoming, engaging email content tailored to the user's profile and use case.
Given the user information:
- Name: {{userName}}
- Industry: {{industry}}
- Company Size: {{companySize}}
- Use Case: {{useCase}}
- Job Title: {{jobTitle}}
Generate personalized email content with:
1. **greeting**: A warm, contextual greeting
2. **personalizedMessage**: One sentence showing we understand their specific needs
3. **introText**: Brief intro paragraph (2-3 sentences)
4. **useCaseTitle**: Heading for use case section
5. **useCases**: Array of 3 relevant use cases with title and description
6. **ctaText**: Compelling CTA button text
7. **closingText**: Friendly closing paragraph
Output as JSON:
```json
{
"greeting": "...",
"personalizedMessage": "...",
"introText": "...",
"useCaseTitle": "...",
"useCases": [
{"title": "...", "description": "..."},
{"title": "...", "description": "..."},
{"title": "...", "description": "..."}
],
"ctaText": "...",
"closingText": "..."
}
Copy
```bash
# Register prompt
edgit components add personalize-email prompts/personalize-email.md prompt \
--metadata '{
"description": "Personalizes welcome email content based on user profile",
"provider": "anthropic",
"model": "claude-3-5-sonnet-20241022",
"temperature": 0.7,
"outputFormat": "json"
}'
# Version and deploy
edgit tag create personalize-email v1.0.0
edgit deploy set personalize-email v1.0.0 --to production
Step 4: Build Campaign Ensemble
Copy
# ensemble.yaml
name: welcome-email-campaign
description: Onboarding email campaign with A/B testing
bindings:
EMAIL:
type: send_email
service: resend
COMPONENTS:
type: kv_namespace
id: components-kv
USERS_DB:
type: d1
database_name: users
ANALYTICS:
type: analytics_engine
dataset: email_campaigns
members:
# Get user profile
- name: get-user-profile
type: Data
config:
query: |
SELECT name, email, industry, company_size, use_case, job_title, segment
FROM users
WHERE id = $1
params:
- ${input.userId}
# Generate personalized content (for segment C)
- name: personalize-content
type: Think
condition: ${member.get-user-profile.output.segment === 'premium'}
config:
prompt: prompt://personalize-email@v1.0.0
provider: anthropic
model: claude-3-5-sonnet-20241022
temperature: 0.7
data:
userName: ${member.get-user-profile.output.name}
industry: ${member.get-user-profile.output.industry}
companySize: ${member.get-user-profile.output.company_size}
useCase: ${member.get-user-profile.output.use_case}
jobTitle: ${member.get-user-profile.output.job_title}
# Determine which template variant to use
- name: assign-variant
type: Transform
config:
code: |
const userId = input.userId;
const segment = member['get-user-profile'].output.segment;
// Premium users always get personalized (v3.0.0)
if (segment === 'premium') {
return {
variant: 'personalized',
templateVersion: 'v3.0.0',
group: 'C-personalized'
};
}
// A/B test for standard users
const hash = userId % 100;
if (hash < 50) {
return {
variant: 'v1',
templateVersion: 'v1.0.0',
group: 'A-control'
};
} else {
return {
variant: 'v2',
templateVersion: 'v2.0.0',
group: 'B-variant'
};
}
# Send email - Variant A (Control)
- name: send-email-v1
type: Email
condition: ${member.assign-variant.output.variant === 'v1'}
config:
template: template://welcome-v1@v1.0.0
from: welcome@myapp.com
to: ${member.get-user-profile.output.email}
subject: Welcome to MyApp
data:
appName: "MyApp"
userName: ${member.get-user-profile.output.name}
activationUrl: https://app.myapp.com/activate?token=${input.activationToken}
companyName: "MyApp Inc"
unsubscribeUrl: https://myapp.com/unsubscribe?email=${member.get-user-profile.output.email}
# Send email - Variant B (Improved CTA)
- name: send-email-v2
type: Email
condition: ${member.assign-variant.output.variant === 'v2'}
config:
template: template://welcome-v2@v2.0.0
from: welcome@myapp.com
to: ${member.get-user-profile.output.email}
subject: "🎉 Your MyApp account is ready!"
data:
appName: "MyApp"
userName: ${member.get-user-profile.output.name}
activationUrl: https://app.myapp.com/activate?token=${input.activationToken}
companyName: "MyApp Inc"
unsubscribeUrl: https://myapp.com/unsubscribe?email=${member.get-user-profile.output.email}
# Send email - Variant C (Personalized)
- name: send-email-personalized
type: Email
condition: ${member.assign-variant.output.variant === 'personalized'}
config:
template: template://welcome-personalized@v3.0.0
from: welcome@myapp.com
to: ${member.get-user-profile.output.email}
subject: ${member.personalize-content.output.personalizedMessage}
data:
subject: ${member.personalize-content.output.personalizedMessage}
greeting: ${member.personalize-content.output.greeting}
userName: ${member.get-user-profile.output.name}
personalizedMessage: ${member.personalize-content.output.personalizedMessage}
introText: ${member.personalize-content.output.introText}
useCaseTitle: ${member.personalize-content.output.useCaseTitle}
useCases: ${member.personalize-content.output.useCases}
activationUrl: https://app.myapp.com/activate?token=${input.activationToken}
ctaText: ${member.personalize-content.output.ctaText}
closingText: ${member.personalize-content.output.closingText}
companyName: "MyApp Inc"
unsubscribeUrl: https://myapp.com/unsubscribe?email=${member.get-user-profile.output.email}
# Track campaign event
- name: track-email-sent
type: Data
config:
query: |
INSERT INTO email_events (user_id, variant, template_version, event_type, timestamp)
VALUES ($1, $2, $3, 'sent', CURRENT_TIMESTAMP)
params:
- ${input.userId}
- ${member.assign-variant.output.group}
- ${member.assign-variant.output.templateVersion}
# Log to Analytics Engine
- name: log-analytics
type: Analytics
config:
dataset: email_campaigns
data:
event: email_sent
userId: ${input.userId}
variant: ${member.assign-variant.output.group}
templateVersion: ${member.assign-variant.output.templateVersion}
segment: ${member.get-user-profile.output.segment}
industry: ${member.get-user-profile.output.industry}
timestamp: ${Date.now()}
output:
success: true
variant: ${member.assign-variant.output.group}
templateVersion: ${member.assign-variant.output.templateVersion}
emailSent: true
Step 5: Deploy Campaign
Copy
# Deploy ensemble
npx wrangler deploy
# Trigger for a new user
curl -X POST https://your-worker.workers.dev/ensembles/welcome-email-campaign \
-H "Content-Type: application/json" \
-d '{
"userId": 12345,
"activationToken": "abc123xyz"
}'
Step 6: A/B Test Results Analysis
Track Activation Rates
Copy
-- Track user activations
INSERT INTO email_events (user_id, variant, template_version, event_type, timestamp)
VALUES ($user_id, $variant, $template_version, 'activated', CURRENT_TIMESTAMP);
-- Calculate conversion rates
SELECT
variant,
template_version,
COUNT(DISTINCT CASE WHEN event_type = 'sent' THEN user_id END) as sent,
COUNT(DISTINCT CASE WHEN event_type = 'activated' THEN user_id END) as activated,
ROUND(100.0 * COUNT(DISTINCT CASE WHEN event_type = 'activated' THEN user_id END) /
COUNT(DISTINCT CASE WHEN event_type = 'sent' THEN user_id END), 2) as conversion_rate
FROM email_events
WHERE timestamp > NOW() - INTERVAL '7 days'
GROUP BY variant, template_version
ORDER BY conversion_rate DESC;
-- Results:
-- variant template_version sent activated conversion_rate
-- C-personalized v3.0.0 500 285 57.00%
-- B-variant v2.0.0 5000 2150 43.00%
-- A-control v1.0.0 5000 1850 37.00%
Results
- Variant A (Control): 37% activation rate
- Variant B (Improved CTA): 43% activation rate (+16% improvement)
- Variant C (Personalized): 57% activation rate (+54% improvement)
Step 7: Progressive Rollout
Based on results, progressively roll out the winner:Copy
# Phase 1: Test with 10% of traffic
edgit deploy set welcome-v2 v2.0.0 --to canary
# Update ensemble to route 10% to canary
# Phase 2: Monitor for 2 days, increase to 25%
# Update routing logic to 25%
# Phase 3: Increase to 50%
# Update routing logic to 50%
# Phase 4: Full rollout to 100%
edgit deploy set welcome-v2 v2.0.0 --to production
# Update ensemble: template://welcome-v2@v2.0.0
# Phase 5: Keep personalized for premium segment
# Premium users continue using: template://welcome-personalized@v3.0.0
Step 8: Continuous Improvement
Test New Subject Lines
Copy
# Add subject line variants
members:
- name: assign-subject
type: Transform
config:
code: |
const subjects = [
"Welcome to MyApp",
"🎉 Your MyApp account is ready!",
"Let's get started with MyApp",
"Your journey with MyApp begins now"
];
const hash = input.userId % subjects.length;
return { subject: subjects[hash], subjectVariant: hash };
- name: send-email-v2
type: Email
config:
template: template://welcome-v2@v2.0.0
subject: ${member.assign-subject.output.subject}
# ... rest of config
Update Templates Without Downtime
Copy
# Fix typo in production template
vim templates/email/welcome-v2.html
# Create patch version
edgit tag create welcome-v2 v2.0.1 \
--message "Fix typo in benefits section"
# Deploy new version (instant, zero downtime)
edgit deploy set welcome-v2 v2.0.1 --to production
# Update ensemble reference
# template://welcome-v2@v2.0.1
Add Seasonal Variations
Copy
# Create holiday variant
cp templates/email/welcome-v2.html templates/email/welcome-holiday.html
vim templates/email/welcome-holiday.html # Add holiday theme
edgit components add welcome-holiday templates/email/welcome-holiday.html template
edgit tag create welcome-holiday v1.0.0
edgit deploy set welcome-holiday v1.0.0 --to production
# Use conditionally
# template://welcome-holiday@v1.0.0 (if holiday season)
# template://welcome-v2@v2.0.1 (regular)
Performance Metrics
Without Component Versioning
Copy
Timeline for template updates:
1. Update template in code
2. Commit to git
3. Run tests
4. Build application
5. Deploy to staging (~10 minutes)
6. Test in staging (~1-2 hours)
7. Deploy to production (~10 minutes)
8. Monitor (~1 day)
Total time to production: ~1-2 days
Rollback time: ~20 minutes (full deployment)
Risk: High (entire app restart)
With Component Versioning (Edgit + Conductor)
Copy
Timeline for template updates:
1. Update template file
2. Version with Edgit (~10 seconds)
3. Deploy to staging (~instant, edge cache update)
4. Test in staging (~30 minutes)
5. Deploy to production (~instant, edge cache update)
6. Monitor (~real-time)
Total time to production: ~30-60 minutes
Rollback time: ~instant (version switch)
Risk: Low (only template changes, no restart)
Key Takeaways
Component Versioning Benefits
- Rapid Iteration: Update templates in minutes, not days
- Safe Testing: A/B test multiple versions simultaneously
- Instant Rollback: Switch versions instantly if issues arise
- Zero Downtime: No application restarts or deployments
- Independent Evolution: Templates evolve separately from code
Best Practices
- Version Everything: Templates, prompts, and content
- Test in Stages: staging → canary → production
- Track Metrics: Measure impact of each version
- Document Changes: Use semantic versioning with clear messages
- Keep History: Never delete old versions (instant rollback capability)
Real-World Impact
- 16% increase in activation rate (v1 → v2)
- 54% increase for premium segment (personalized)
- 95% faster template iteration cycle
- Zero downtime deployments
- Instant rollback capability

