diff --git a/apps/website/emails/drip-whitepaper-followup.ts b/apps/website/emails/drip-whitepaper-followup.ts new file mode 100644 index 00000000..7fcd5dbf --- /dev/null +++ b/apps/website/emails/drip-whitepaper-followup.ts @@ -0,0 +1,52 @@ +export function dripWhitepaperFollowupHtml(day: number): { subject: string; html: string } { + const unsubUrl = 'https://stream-resource.dev/api/unsubscribe'; + + 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.

+ `), + }; + } + 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 →

+ `), + }; + } + 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 →

+ `), + }; + } + // 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.

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

Angular Agent Framework

+ ${body} +
+

Angular Agent Framework — Signal-native streaming for LangGraph.

+

Unsubscribe

+
`; +} diff --git a/apps/website/lib/drip.ts b/apps/website/lib/drip.ts new file mode 100644 index 00000000..53a05102 --- /dev/null +++ b/apps/website/lib/drip.ts @@ -0,0 +1,31 @@ +import { sendEmail, FROM } from './resend'; +import { dripWhitepaperFollowupHtml } from '../emails/drip-whitepaper-followup'; + +const DRIP_DAYS = [2, 5, 10, 20]; + +function daysFromNow(days: number): string { + const d = new Date(); + d.setDate(d.getDate() + days); + d.setHours(9, 0, 0, 0); // Send at 9am + return d.toISOString(); +} + +/** Schedule the whitepaper drip sequence for a contact. Best-effort. */ +export async function scheduleWhitepaperDrip(email: string) { + for (const day of DRIP_DAYS) { + const { subject, html } = dripWhitepaperFollowupHtml(day); + // Replace RECIPIENT placeholder with actual email for unsubscribe link + const personalizedHtml = html.replace('email=RECIPIENT', `email=${encodeURIComponent(email)}`); + try { + await sendEmail({ + from: FROM, + to: email, + subject, + html: personalizedHtml, + scheduledAt: daysFromNow(day), + }); + } catch (err) { + console.error(`[drip] Failed to schedule day-${day} email for ${email}:`, err); + } + } +} diff --git a/apps/website/lib/resend.ts b/apps/website/lib/resend.ts index 85e60cf6..086b9d8f 100644 --- a/apps/website/lib/resend.ts +++ b/apps/website/lib/resend.ts @@ -15,13 +15,19 @@ export const FROM = process.env.RESEND_FROM || 'Angular Agent Framework Unsubscribed + +
+

You've been unsubscribed

+

You won't receive any more emails from us.

+
`, + { headers: { 'Content-Type': 'text/html' } } + ); +} diff --git a/apps/website/src/app/api/whitepaper-signup/route.ts b/apps/website/src/app/api/whitepaper-signup/route.ts index 172166b7..6af9447f 100644 --- a/apps/website/src/app/api/whitepaper-signup/route.ts +++ b/apps/website/src/app/api/whitepaper-signup/route.ts @@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'; import fs from 'fs'; import path from 'path'; import { sendEmail, FROM, addToAudience } from '../../../../lib/resend'; -import { loopsUpsertContact, loopsSendEvent } from '../../../../lib/loops'; +import { scheduleWhitepaperDrip } from '../../../../lib/drip'; import { whitepaperDownloadHtml } from '../../../../emails/whitepaper-download'; const SIGNUPS_FILE = path.join(process.cwd(), 'data', 'whitepaper-signups.ndjson'); @@ -40,15 +40,7 @@ export async function POST(req: NextRequest) { html: whitepaperDownloadHtml(name || undefined), }), addToAudience(email, name || undefined), - loopsUpsertContact({ - email, - firstName: name || undefined, - source: 'whitepaper', - }), - loopsSendEvent({ - email, - eventName: 'whitepaper_downloaded', - }), + scheduleWhitepaperDrip(email), ]); } catch (err) { console.error('[resend] whitepaper email failed:', err);