diff --git a/apps/website/emails/drip-whitepaper-followup.ts b/apps/website/emails/drip-whitepaper-followup.ts index 7fcd5dbf0..dc45d4e91 100644 --- a/apps/website/emails/drip-whitepaper-followup.ts +++ b/apps/website/emails/drip-whitepaper-followup.ts @@ -1,52 +1,66 @@ -export function dripWhitepaperFollowupHtml(day: number): { subject: string; html: string } { - const unsubUrl = 'https://stream-resource.dev/api/unsubscribe'; +import { wrapEmail } from './email-wrapper'; +export function dripWhitepaperFollowupHtml(day: number): { subject: string; html: string } { if (day === 2) { return { subject: 'Did you get a chance to read Chapter 3?', - html: wrapEmail(` -

Chapter 3 covers tool-call rendering — how to surface agent actions as real UI instead of raw JSON. It's the chapter most teams bookmark first.

-

If you haven't downloaded it yet, grab it here.

- `), + html: wrapEmail({ + body: ` +

Whitepaper Follow-up

+

Did you get a chance to read Chapter 3?

+

Chapter 3 covers tool-call rendering — how to surface agent actions as real UI instead of raw JSON. It's the chapter most teams bookmark first.

+ Read the Guide → + `, + showUnsubscribe: true, + }), }; } + if (day === 5) { return { subject: 'The gap between demo and production', - html: wrapEmail(` -

Half of GenAI projects die after proof of concept. The gap isn't the model — it's the frontend production path: streaming state, thread persistence, human approval flows, and deterministic testing.

-

That's exactly what the Angular Agent Framework solves. See how it works →

- `), + html: wrapEmail({ + body: ` +

Production Readiness

+

The gap between demo and production

+

Half of GenAI projects die after proof of concept. The gap isn't the model — it's the frontend production path: streaming state, thread persistence, human approval flows, and deterministic testing.

+ See How It Works → + `, + showUnsubscribe: true, + }), }; } + if (day === 10) { return { subject: 'The pilot program is included with every app license', - html: wrapEmail(` -

Every app deployment license includes a 3-month co-pilot engagement — we work alongside your Angular team to ship your first agent to production.

-

Week 1: Integration & first stream
Month 1: First agent in staging
Month 3: Production deployment

-

Learn about the pilot program →

- `), + html: wrapEmail({ + body: ` +

Pilot Program

+

The pilot program is included with every app license

+

Every app deployment license includes a 3-month co-pilot engagement — we work alongside your Angular team to ship your first agent to production.

+
+

Week 1 · Integration & first stream

+

Month 1 · First agent in staging

+

Month 3 · Production deployment

+
+ Learn About the Pilot → + `, + showUnsubscribe: true, + }), }; } + // day === 20 return { - subject: 'Ready to ship your agent? Let\'s talk.', - html: wrapEmail(` -

If your team is evaluating how to take an Angular + LangGraph agent to production, I'd love to hear what you're building.

-

Reply to this email or schedule a conversation — no pitch, just a technical discussion about your use case.

- `), + subject: "Ready to ship your agent? Let's talk.", + html: wrapEmail({ + body: ` +

Let's Connect

+

Ready to ship your agent? Let's talk.

+

If your team is evaluating how to take an Angular + LangGraph agent to production, I'd love to hear what you're building. Reply to this email or schedule a conversation — no pitch, just a technical discussion about your use case.

+ `, + showUnsubscribe: true, + }), }; } - -function wrapEmail(body: string): string { - return ` - -
-

Angular Agent Framework

- ${body} -
-

Angular Agent Framework — Signal-native streaming for LangGraph.

-

Unsubscribe

-
`; -} diff --git a/apps/website/emails/email-wrapper.ts b/apps/website/emails/email-wrapper.ts new file mode 100644 index 000000000..77bb9d1eb --- /dev/null +++ b/apps/website/emails/email-wrapper.ts @@ -0,0 +1,28 @@ +/** + * Shared HTML wrapper for all email templates. + * Gradient header band with logo, white body, footer. + */ +export function wrapEmail(opts: { + body: string; + showUnsubscribe?: boolean; +}): string { + return ` + +
+
+
🛩️ Angular Agent Framework
+
+
+ ${opts.body} +
+

Angular Agent Framework — Signal-native streaming for LangGraph.

+ ${opts.showUnsubscribe ? '

Unsubscribe

' : ''} +
+
+
+`; +} + +export function esc(s: string): string { + return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); +} diff --git a/apps/website/emails/lead-notification.ts b/apps/website/emails/lead-notification.ts index 0edecf526..b5475dd96 100644 --- a/apps/website/emails/lead-notification.ts +++ b/apps/website/emails/lead-notification.ts @@ -1,3 +1,5 @@ +import { wrapEmail, esc } from './email-wrapper'; + interface LeadNotificationProps { name: string; email: string; @@ -7,18 +9,15 @@ interface LeadNotificationProps { } export function leadNotificationHtml({ name, email, company, message, ts }: LeadNotificationProps): string { - return ` - -
-

New Lead

-

${esc(name)}

-

${esc(email)}${company ? ` — ${esc(company)}` : ''}

- ${message ? `

${esc(message)}

` : ''} -
-

Received ${esc(ts)}

-
`; -} - -function esc(s: string): string { - return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + return wrapEmail({ + body: ` +

New Lead

+

${esc(name)}

+

${esc(email)}${company ? ` — ${esc(company)}` : ''}

+ ${message ? `

${esc(message)}

` : ''} +
+

Received ${esc(ts)}

+
+ `, + }); } diff --git a/apps/website/emails/newsletter-welcome.ts b/apps/website/emails/newsletter-welcome.ts index d90d28dac..f8701ba05 100644 --- a/apps/website/emails/newsletter-welcome.ts +++ b/apps/website/emails/newsletter-welcome.ts @@ -1,12 +1,11 @@ +import { wrapEmail } from './email-wrapper'; + export function newsletterWelcomeHtml(): string { - return ` - -
-

Angular Agent Framework

-

Welcome to Angular Agent Framework updates

-

You'll receive updates on new capabilities, production patterns, and Angular agent best practices. We keep it focused and infrequent — no spam.

- Explore the Docs -
-

Angular Agent Framework — Signal-native streaming for LangGraph.

-
`; + return wrapEmail({ + body: ` +

Welcome to Angular Agent Framework updates

+

You'll receive updates on new capabilities, production patterns, and Angular agent best practices. We keep it focused and infrequent — no spam.

+ Explore the Docs + `, + }); } diff --git a/apps/website/emails/whitepaper-download.ts b/apps/website/emails/whitepaper-download.ts index c57487368..4c0141b40 100644 --- a/apps/website/emails/whitepaper-download.ts +++ b/apps/website/emails/whitepaper-download.ts @@ -1,20 +1,15 @@ +import { wrapEmail, esc } from './email-wrapper'; + const DOWNLOAD_URL = 'https://stream-resource.dev/whitepaper.pdf'; export function whitepaperDownloadHtml(name?: string): string { - return ` - -
-

Angular Agent Framework

-

Your Angular Agent Readiness Guide

-

${name ? `Hi ${esc(name)}, t` : 'T'}he guide covers six production-readiness dimensions: streaming state, thread persistence, tool-call rendering, human approval flows, generative UI, and deterministic testing.

-
- Download the Guide -
-
-

Angular Agent Framework — Signal-native streaming for LangGraph.

-
`; -} - -function esc(s: string): string { - return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + return wrapEmail({ + body: ` +

Your Angular Agent Readiness Guide

+

${name ? `Hi ${esc(name)}, t` : 'T'}he guide covers six production-readiness dimensions: streaming state, thread persistence, tool-call rendering, human approval flows, generative UI, and deterministic testing.

+
+ Download the Guide +
+ `, + }); } diff --git a/apps/website/src/app/api/email-preview/route.ts b/apps/website/src/app/api/email-preview/route.ts new file mode 100644 index 000000000..ca2f5f712 --- /dev/null +++ b/apps/website/src/app/api/email-preview/route.ts @@ -0,0 +1,81 @@ +/** + * Dev-only email template preview route. + * Visit /api/email-preview?template=whitepaper-download to preview a template. + * Available templates: whitepaper-download, newsletter-welcome, lead-notification, + * drip-day-2, drip-day-5, drip-day-10, drip-day-20 + */ +import { NextRequest, NextResponse } from 'next/server'; +import { whitepaperDownloadHtml } from '../../../../emails/whitepaper-download'; +import { newsletterWelcomeHtml } from '../../../../emails/newsletter-welcome'; +import { leadNotificationHtml } from '../../../../emails/lead-notification'; +import { dripWhitepaperFollowupHtml } from '../../../../emails/drip-whitepaper-followup'; + +const TEMPLATES: Record { subject: string; html: string }> = { + 'whitepaper-download': () => ({ + subject: 'Your Angular Agent Readiness Guide', + html: whitepaperDownloadHtml('Brian'), + }), + 'newsletter-welcome': () => ({ + subject: 'Welcome to Angular Agent Framework updates', + html: newsletterWelcomeHtml(), + }), + 'lead-notification': () => ({ + subject: 'New lead: Brian at Cacheplane', + html: leadNotificationHtml({ + name: 'Brian Love', + email: 'brian@cacheplane.io', + company: 'Cacheplane', + message: 'Interested in the pilot program for our Angular + LangGraph project.', + ts: new Date().toISOString(), + }), + }), + 'drip-day-2': () => dripWhitepaperFollowupHtml(2), + 'drip-day-5': () => dripWhitepaperFollowupHtml(5), + 'drip-day-10': () => dripWhitepaperFollowupHtml(10), + 'drip-day-20': () => dripWhitepaperFollowupHtml(20), +}; + +export async function GET(req: NextRequest) { + const template = req.nextUrl.searchParams.get('template'); + + // Index page — show all templates + if (!template) { + const links = Object.keys(TEMPLATES).map( + (t) => `${t}` + ).join(''); + + return new NextResponse( + `Email Previews + +

Email Template Previews

+

Click a template to preview it as rendered HTML.

+ ${links} + `, + { headers: { 'Content-Type': 'text/html' } } + ); + } + + const factory = TEMPLATES[template]; + if (!factory) { + return new NextResponse(`Unknown template: ${template}`, { status: 404 }); + } + + const { subject, html } = factory(); + + // Wrap in a preview frame showing subject line + const preview = `Preview: ${subject} + +
+
+ Subject: + ${subject} +
+ ← All templates +
+
+ ${html} +
+ `; + + return new NextResponse(preview, { headers: { 'Content-Type': 'text/html' } }); +}