Skip to main content

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:
  1. Testing different email designs
  2. Personalizing content based on user segment
  3. Measuring which version drives more activations
  4. Rolling out winners gradually without code deployments

Project Structure

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)

<!-- 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)

<!-- 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)

<!-- 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

# 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

<!-- 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": "..."
}
Make it conversational, benefit-focused, and industry-appropriate.

```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

# 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

# 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

-- 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)
Winner: Variant B for general audience, Variant C for premium segment

Step 7: Progressive Rollout

Based on results, progressively roll out the winner:
# 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

# 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

# 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

# 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

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)

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)
Result: 95% faster iteration, 100% safer deployments

Key Takeaways

Component Versioning Benefits

  1. Rapid Iteration: Update templates in minutes, not days
  2. Safe Testing: A/B test multiple versions simultaneously
  3. Instant Rollback: Switch versions instantly if issues arise
  4. Zero Downtime: No application restarts or deployments
  5. Independent Evolution: Templates evolve separately from code

Best Practices

  1. Version Everything: Templates, prompts, and content
  2. Test in Stages: staging → canary → production
  3. Track Metrics: Measure impact of each version
  4. Document Changes: Use semantic versioning with clear messages
  5. 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

Next Steps