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