Customization

Last updated on 2026-08-27

The Email Marketing Kit is designed to be customized. Every aspect of the UI -- from colors and fonts to data structure and block types -- can be modified to match your product.

Change the Primary Hue

The entire color scheme is driven by a single hue value. To change the accent color, update the hue in app/globals.css:

Step 1: Find the Primary Hue

The primary color uses hue 115 (chartreuse-green):

/* app/globals.css */
--primary: oklch(0.48 0.14 115);

Step 2: Replace the Hue

Change 115 to your desired hue across all tokens that reference it. Here are all the tokens that use the accent hue:

:root {
  /* Replace 115 with your hue (e.g., 220 for blue, 350 for red, 280 for purple) */
  --primary: oklch(0.48 0.14 YOUR_HUE);
  --primary-foreground: oklch(0.98 0.005 YOUR_HUE);
  --accent-foreground: oklch(0.35 0.12 YOUR_HUE);
  --ring: oklch(0.48 0.14 YOUR_HUE);
  --chart-1: oklch(0.48 0.14 YOUR_HUE);
  --sidebar-primary: oklch(0.48 0.14 YOUR_HUE);
  --sidebar-primary-foreground: oklch(0.98 0.005 YOUR_HUE);
  --sidebar-accent-foreground: oklch(0.35 0.12 YOUR_HUE);
  --sidebar-ring: oklch(0.48 0.14 YOUR_HUE);
}

.dark {
  --primary: oklch(0.72 0.14 YOUR_HUE);
  --primary-foreground: oklch(0.13 0.02 YOUR_HUE);
  --ring: oklch(0.72 0.14 YOUR_HUE);
  --chart-1: oklch(0.72 0.14 YOUR_HUE);
  --sidebar-primary: oklch(0.72 0.14 YOUR_HUE);
  --sidebar-primary-foreground: oklch(0.13 0.02 YOUR_HUE);
  --sidebar-ring: oklch(0.72 0.14 YOUR_HUE);
}
Hue Color Example
115 Chartreuse-green (default) oklch(0.48 0.14 115)
220 Blue oklch(0.48 0.14 220)
280 Purple oklch(0.48 0.14 280)
350 Red oklch(0.48 0.14 350)
30 Orange oklch(0.48 0.14 30)
180 Teal oklch(0.48 0.14 180)
150 Green oklch(0.48 0.14 150)

Swap Fonts

Fonts are loaded in app/layout.tsx using next/font/google and applied via CSS variables.

Step 1: Import New Fonts

// app/layout.tsx
import { Poppins, Space_Grotesk, Fira_Code } from "next/font/google"

const headingFont = Space_Grotesk({
  variable: "--font-familjen",  // Keep the same variable name
  subsets: ["latin"],
})

const bodyFont = Poppins({
  variable: "--font-inter-tight",
  subsets: ["latin"],
  weight: ["400", "500", "600"],
})

const monoFont = Fira_Code({
  variable: "--font-plex-mono",
  subsets: ["latin"],
  weight: ["400", "500"],
})

Step 2: Apply to Body

The variable names are already wired to globals.css, so the fonts will apply automatically as long as you reuse the same CSS variable names (--font-familjen, --font-inter-tight, --font-plex-mono).

Alternatively, update the CSS variable mappings in globals.css:

@theme inline {
  --font-sans: var(--font-your-body), ui-sans-serif, system-ui, sans-serif;
  --font-heading: var(--font-your-heading), ui-sans-serif, system-ui, sans-serif;
  --font-mono: var(--font-your-mono), ui-monospace, monospace;
}

Modify Seed Data Structure

All mock data lives in data/seed.ts. To modify the data:

Add New Fields to Existing Types

  1. Update the interface in types/index.ts:
// types/index.ts
export interface Campaign {
  // ...existing fields
  priority: 'high' | 'medium' | 'low';  // New field
}
  1. Add the field to seed data:
// data/seed.ts
export const campaigns: Campaign[] = [
  {
    id: "cam_1",
    name: "Summer Sale",
    // ...existing data
    priority: "high",  // New field
  },
]
  1. Use the field in your page:
// In any page.tsx
<Badge variant={campaign.priority === 'high' ? 'destructive' : 'default'}>
  {campaign.priority}
</Badge>

Add Entirely New Data Sets

  1. Define the type in types/index.ts
  2. Export the data array from data/seed.ts
  3. Import and use in your page files

Add New Email Block Types

The email editor supports custom block types. To add a new block:

Step 1: Extend the Type

// types/index.ts
export interface EmailBlock {
  id: string;
  type: 'header' | 'hero' | 'footer' | 'text' | 'image' | 'button'
       | 'divider' | 'columns' | 'social' | 'video' | 'html'
       | 'product' | 'countdown' | 'menu'
       | 'testimonial';  // New block type
  content?: Record<string, unknown>;
  config?: Record<string, unknown>;
  styles?: EmailBlockStyles;
}

Step 2: Add Seed Data

// data/seed.ts
export const emailBlocks: EmailBlock[] = [
  // ...existing blocks
  {
    id: "block_testimonial_1",
    type: "testimonial",
    content: {
      quote: "This product changed my workflow completely.",
      author: "Jane Smith",
      role: "Product Manager",
      avatar: "/avatars/jane.jpg",
    },
    styles: {
      backgroundColor: "#f9fafb",
      padding: "24px",
      borderRadius: "8px",
    },
  },
]

Step 3: Add Block Rendering

In app/(app)/campaigns/builder/page.tsx, add a case for the new block type in the block rendering logic:

function renderBlock(block: EmailBlock) {
  switch (block.type) {
    // ...existing cases
    case 'testimonial':
      return (
        <div className="p-6 bg-muted rounded-lg">
          <blockquote className="text-lg italic">
            "{block.content?.quote}"
          </blockquote>
          <p className="mt-2 font-medium">{block.content?.author}</p>
          <p className="text-sm text-muted-foreground">{block.content?.role}</p>
        </div>
      )
  }
}

Step 4: Add Block to Palette

Add the new block type to the block palette in the editor sidebar:

const blockPalette = [
  // ...existing blocks
  { type: 'testimonial', icon: Quote, label: 'Testimonial' },
]

Add New Automation Node Types

Step 1: Extend the Type

// types/index.ts
export interface FlowNode {
  type: 'trigger' | 'email' | 'wait' | 'split' | 'webhook'
       | 'tag' | 'move-to-list' | 'condition'
       | 'sms';  // New node type
  // ...rest of interface
}

Step 2: Add Node Rendering

In app/(app)/automations/[id]/page.tsx, add rendering and configuration for the new node type:

function getNodeIcon(type: string) {
  switch (type) {
    // ...existing cases
    case 'sms': return MessageSquare
  }
}

function getNodeColor(type: string) {
  switch (type) {
    // ...existing cases
    case 'sms': return 'bg-purple-100 text-purple-700'
  }
}

Connect a Real ESP (Email Service Provider)

The kit uses seed data by default. To connect a real email backend:

SendGrid

// lib/email.ts
import sgMail from '@sendgrid/mail'

sgMail.setApiKey(process.env.SENDGRID_API_KEY!)

export async function sendCampaign(campaign: Campaign, recipients: string[]) {
  const msg = {
    to: recipients,
    from: campaign.fromEmail || 'noreply@yourdomain.com',
    subject: campaign.subject,
    html: renderEmailBlocks(campaign.blocks),
  }
  return sgMail.sendMultiple(msg)
}

Resend

// lib/email.ts
import { Resend } from 'resend'

const resend = new Resend(process.env.RESEND_API_KEY!)

export async function sendCampaign(campaign: Campaign, recipients: string[]) {
  return resend.emails.send({
    from: campaign.fromEmail || 'noreply@yourdomain.com',
    to: recipients,
    subject: campaign.subject,
    html: renderEmailBlocks(campaign.blocks),
  })
}

AWS SES

// lib/email.ts
import { SESClient, SendEmailCommand } from '@aws-sdk/client-ses'

const ses = new SESClient({ region: process.env.AWS_REGION })

export async function sendCampaign(campaign: Campaign, recipients: string[]) {
  const command = new SendEmailCommand({
    Source: campaign.fromEmail || 'noreply@yourdomain.com',
    Destination: { ToAddresses: recipients },
    Message: {
      Subject: { Data: campaign.subject },
      Body: { Html: { Data: renderEmailBlocks(campaign.blocks) } },
    },
  })
  return ses.send(command)
}

Add Authentication

The kit does not include authentication by default. Here are common options:

NextAuth.js

pnpm add next-auth

Wrap the app layout with a session provider and add middleware for protected routes.

Clerk

pnpm add @clerk/nextjs

Add the Clerk provider to app/layout.tsx and use authMiddleware() in middleware.ts.

Supabase Auth

pnpm add @supabase/supabase-js @supabase/ssr

Create a Supabase client and add authentication checks to the (app) layout.


Connect a Database

Replace seed data with real database queries:

Prisma

pnpm add prisma @prisma/client
npx prisma init

Define your schema matching the TypeScript interfaces in types/index.ts, then replace seed data imports with Prisma queries.

Drizzle

pnpm add drizzle-orm drizzle-kit

Supabase

pnpm add @supabase/supabase-js

Migration Strategy

  1. Keep the seed data as a reference for the expected data shape
  2. Create database tables matching the interfaces in types/index.ts
  3. Replace import { campaigns } from "@/data/seed" with database queries
  4. Use React Server Components for data fetching in page files