Skip to content

Commit cd662c7

Browse files
authored
Merge pull request #98 from cacheplane/fix/wire-whitepaper-drip
feat(website): wire whitepaper signup to Resend email pipeline + drip sequences
2 parents 1e0cab1 + 309010d commit cd662c7

4 files changed

Lines changed: 75 additions & 19 deletions

File tree

apps/website/lib/drip.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
import { sendEmail, FROM } from './resend';
22
import { dripWhitepaperFollowupHtml } from '../emails/drip-whitepaper-followup';
3+
import { dripAngularFollowupHtml } from '../emails/drip-angular-followup';
4+
import { dripRenderFollowupHtml } from '../emails/drip-render-followup';
5+
import { dripChatFollowupHtml } from '../emails/drip-chat-followup';
6+
7+
export type PaperId = 'overview' | 'angular' | 'render' | 'chat';
38

49
const DRIP_DAYS = [2, 5, 10, 20];
510

11+
const DRIP_GENERATORS: Record<PaperId, (day: number) => { subject: string; html: string }> = {
12+
overview: dripWhitepaperFollowupHtml,
13+
angular: dripAngularFollowupHtml,
14+
render: dripRenderFollowupHtml,
15+
chat: dripChatFollowupHtml,
16+
};
17+
618
function daysFromNow(days: number): string {
719
const d = new Date();
820
d.setDate(d.getDate() + days);
@@ -11,10 +23,10 @@ function daysFromNow(days: number): string {
1123
}
1224

1325
/** Schedule the whitepaper drip sequence for a contact. Best-effort. */
14-
export async function scheduleWhitepaperDrip(email: string) {
26+
export async function scheduleWhitepaperDrip(email: string, paper: PaperId = 'overview') {
27+
const generator = DRIP_GENERATORS[paper] ?? DRIP_GENERATORS.overview;
1528
for (const day of DRIP_DAYS) {
16-
const { subject, html } = dripWhitepaperFollowupHtml(day);
17-
// Replace RECIPIENT placeholder with actual email for unsubscribe link
29+
const { subject, html } = generator(day);
1830
const personalizedHtml = html.replace('email=RECIPIENT', `email=${encodeURIComponent(email)}`);
1931
try {
2032
await sendEmail({
@@ -25,7 +37,7 @@ export async function scheduleWhitepaperDrip(email: string) {
2537
scheduledAt: daysFromNow(day),
2638
});
2739
} catch (err) {
28-
console.error(`[drip] Failed to schedule day-${day} email for ${email}:`, err);
40+
console.error(`[drip] Failed to schedule day-${day} ${paper} email for ${email}:`, err);
2941
}
3042
}
3143
}

apps/website/public/assets/arch-diagram.svg

Lines changed: 8 additions & 8 deletions
Loading

apps/website/public/assets/hero.svg

Lines changed: 3 additions & 3 deletions
Loading

apps/website/src/app/api/whitepaper-signup/route.ts

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,32 @@
1-
// apps/website/src/app/api/whitepaper-signup/route.ts
21
import { NextRequest, NextResponse } from 'next/server';
32
import fs from 'fs';
43
import path from 'path';
4+
import { sendEmail, FROM, addToAudience } from '../../../../lib/resend';
5+
import { loopsUpsertContact, loopsSendEvent } from '../../../../lib/loops';
6+
import { scheduleWhitepaperDrip, type PaperId } from '../../../../lib/drip';
7+
import { whitepaperDownloadHtml } from '../../../../emails/whitepaper-download';
8+
import { angularDownloadHtml } from '../../../../emails/angular-download';
9+
import { renderDownloadHtml } from '../../../../emails/render-download';
10+
import { chatDownloadHtml } from '../../../../emails/chat-download';
511

612
const SIGNUPS_FILE = path.join(process.cwd(), 'data', 'whitepaper-signups.ndjson');
713

14+
const VALID_PAPERS: PaperId[] = ['overview', 'angular', 'render', 'chat'];
15+
16+
const DOWNLOAD_EMAILS: Record<PaperId, (name?: string) => string> = {
17+
overview: whitepaperDownloadHtml,
18+
angular: angularDownloadHtml,
19+
render: renderDownloadHtml,
20+
chat: chatDownloadHtml,
21+
};
22+
23+
const DOWNLOAD_SUBJECTS: Record<PaperId, string> = {
24+
overview: 'Your Angular Agent Readiness Guide',
25+
angular: 'Your Enterprise Guide to Agent Streaming',
26+
render: 'Your Enterprise Guide to Generative UI',
27+
chat: 'Your Enterprise Guide to Agent Chat Interfaces',
28+
};
29+
830
export async function POST(req: NextRequest) {
931
let body: { name?: string; email?: string; paper?: string };
1032
try {
@@ -13,18 +35,40 @@ export async function POST(req: NextRequest) {
1335
return NextResponse.json({ error: 'Invalid JSON' }, { status: 400 });
1436
}
1537

16-
const { name = '', email = '', paper = 'overview' } = body;
38+
const name = (body.name || '').trim().slice(0, 200);
39+
const email = (body.email || '').trim().slice(0, 320);
40+
const paper = (VALID_PAPERS.includes(body.paper as PaperId) ? body.paper : 'overview') as PaperId;
41+
1742
if (!email || !email.includes('@')) {
1843
return NextResponse.json({ error: 'Valid email required' }, { status: 400 });
1944
}
2045

21-
const entry = JSON.stringify({ name: name.trim(), email: email.trim(), paper: paper.trim(), ts: new Date().toISOString() }) + '\n';
46+
// Persist signup to NDJSON (always, even if email fails)
47+
const entry = JSON.stringify({ name, email, paper, ts: new Date().toISOString() }) + '\n';
2248
try {
2349
fs.mkdirSync(path.dirname(SIGNUPS_FILE), { recursive: true });
2450
fs.appendFileSync(SIGNUPS_FILE, entry, 'utf8');
2551
} catch (err) {
2652
console.error('Failed to write signup:', err);
27-
return NextResponse.json({ error: 'Internal error' }, { status: 500 });
53+
}
54+
55+
// Send download confirmation + schedule drip + sync contacts (best-effort)
56+
try {
57+
const downloadHtml = DOWNLOAD_EMAILS[paper](name || undefined);
58+
await Promise.all([
59+
sendEmail({
60+
from: FROM,
61+
to: email,
62+
subject: DOWNLOAD_SUBJECTS[paper],
63+
html: downloadHtml,
64+
}),
65+
scheduleWhitepaperDrip(email, paper),
66+
addToAudience(email, name || undefined),
67+
loopsUpsertContact({ email, firstName: name || undefined, source: `whitepaper-${paper}` }),
68+
loopsSendEvent({ email, eventName: 'whitepaper_downloaded', properties: { paper } }),
69+
]);
70+
} catch (err) {
71+
console.error('[whitepaper-signup] email pipeline failed:', err);
2872
}
2973

3074
return NextResponse.json({ ok: true });

0 commit comments

Comments
 (0)