zle-orm';
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
export class ReferralService {
/**
- Generates a unique referral link for a subscriber.
- In production, use a shortening service for cleaner URLs.
*/
async generateReferralLink(subscriberId: string): Promise<string> {
const subscriber = await db.query.subscribers.findFirst({
where: eq(subscribers.id, subscriberId),
});
if (!subscriber) throw new Error('Subscriber not found');
const token = crypto.randomUUID();
await db.insert(referrals).values({
subscriberId,
token,
createdAt: new Date(),
});
return `${process.env.APP_URL}/subscribe?ref=${token}`;
}
/**
- Tracks a referral click and attributes it upon subscription.
*/
async trackReferralClick(token: string): Promise<void> {
// Log click for analytics; do not create subscriber yet.
await db.referralClicks.create({
data: { token, clickedAt: new Date() },
});
}
/**
- Processes a new subscription and attributes credit to the referrer.
*/
async processSubscription(
email: string,
refToken?: string
): Promise<{ subscriberId: string; creditedReferrerId?: string }> {
// 1. Create subscriber
const result = await db.subscribers.create({
data: { email, status: 'active', subscribedAt: new Date() },
});
let creditedReferrerId: string | undefined;
// 2. Attribute referral if token exists
if (refToken) {
const referral = await db.referrals.findFirst({
where: eq(referrals.token, refToken),
include: { subscriber: true },
});
if (referral) {
creditedReferrerId = referral.subscriberId;
// Update referral status
await db.referrals.update({
where: { id: referral.id },
data: { convertedAt: new Date(), newSubscriberId: result.id },
});
// 3. Trigger reward
await this.triggerReward(referral.subscriberId);
}
}
return { subscriberId: result.id, creditedReferrerId };
}
/**
- Sends a reward email to the referrer.
*/
private async triggerReward(referrerId: string): Promise<void> {
const referrer = await db.subscribers.findFirst({
where: eq(subscribers.id, referrerId),
});
if (!referrer) return;
await resend.emails.send({
from: 'Growth Bot <growth@codcompass.dev>',
to: referrer.email,
subject: 'π You earned a reward!',
html: `<p>Thanks for referring a new subscriber. Here is your reward: [Access Premium Content / Swag / Shoutout].</p>`,
});
}
}
### Implementation: Automated Onboarding Sequence
This webhook handler processes subscription events to trigger immediate onboarding.
```typescript
// src/api/webhooks/subscribe.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { OnboardingService } from '@/services/OnboardingService';
const onboarding = new OnboardingService();
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const { email, referrer, source } = req.body;
// Validate source signature (e.g., from Resend/Dub)
const isValid = verifyWebhookSignature(req);
if (!isValid) return res.status(401).json({ error: 'Invalid signature' });
// Process subscription and referral
const result = await onboarding.processNewSubscriber({
email,
referrer,
source,
});
// Trigger immediate welcome sequence
await onboarding.sendWelcomeSequence(result.subscriberId);
// Update analytics
await onboarding.trackAcquisition(source, result.creditedReferrerId);
res.status(200).json({ success: true, subscriberId: result.subscriberId });
} catch (error) {
console.error('Webhook processing failed:', error);
res.status(500).json({ error: 'Processing failed' });
}
}
Architecture Rationale
- Type Safety: Using TypeScript with Drizzle ORM ensures type safety across the referral schema, reducing runtime errors in attribution logic.
- Decoupled Rewards: Rewards are triggered asynchronously after successful subscription, preventing race conditions and ensuring data consistency.
- Webhook Verification: Verifying signatures prevents malicious actors from spoofing subscription events to farm rewards.
- Source Tracking: Capturing
source allows for granular analysis of which channels (GitHub, Twitter, Blog) yield the highest quality subscribers.
Pitfall Guide
1. Ignoring Deliverability Infrastructure
Mistake: Sending emails without configuring SPF, DKIM, and DMARC records.
Impact: Emails land in spam folders, killing open rates and domain reputation.
Best Practice: Implement strict DMARC policies (p=reject or p=quarantine). Use a dedicated subdomain (e.g., news.yourdomain.com) to isolate reputation.
2. Manual Referral Tracking
Mistake: Asking subscribers to "tag a friend" manually without automated tracking.
Impact: High friction, low participation, and inability to measure ROI.
Best Practice: Automate attribution via unique tokens. Reduce the effort to zero for the user.
3. Over-Automating Personalization
Mistake: Using dynamic content that feels robotic or generic.
Impact: Subscribers disengage due to lack of human connection.
Best Practice: Use automation for logistics (onboarding, rewards) but keep core content personal. Segment by technical interest, not just demographics.
4. Single Point of Failure in Stack
Mistake: Relying on a single provider for both hosting and email delivery.
Impact: If the provider goes down, you lose access to your list and communication channel.
Best Practice: Maintain a local database of subscribers. Use the email provider strictly for delivery. Export backups weekly.
5. Focusing on Vanity Metrics
Mistake: Optimizing for total subscriber count rather than active engagement.
Impact: High churn, poor deliverability, and inflated CAC.
Best Practice: Track "Active Subscribers" (opened email in last 30 days). Prune inactive subscribers to maintain list health.
6. GDPR/CCPA Non-Compliance
Mistake: Storing subscriber data without consent records or easy unsubscribe mechanisms.
Impact: Legal risk and provider account suspension.
Best Practice: Store explicit consent timestamps. Implement one-click unsubscribe headers. Provide data deletion endpoints.
7. No Feedback Loop for Content
Mistake: Not using subscriber behavior to inform content strategy.
Impact: Content drifts away from audience interests.
Best Practice: Analyze click-through rates on specific topics. Survey subscribers quarterly. Iterate content based on data, not intuition.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Solopreneur (<1k subs) | Resend + Plunk + Manual Code | Low cost, high control, sufficient automation. | ~$20/mo |
| Growth Stage (1k-10k) | Resend + Custom DB + Referral Service | Scalable referral loops, data ownership, advanced segmentation. | ~$50/mo + Dev Time |
| Enterprise/Team (>10k) | Enterprise ESP (e.g., Customer.io) + CDP | Advanced attribution, multi-channel orchestration, compliance tools. | $500+/mo |
| Dev Tool Integration | GitHub/Discord Sync via Zapier/Make | Leverages existing developer communities for acquisition. | ~$30/mo |
Configuration Template
Copy this configuration to initialize your newsletter growth stack.
// src/config/newsletter.ts
export const newsletterConfig = {
provider: {
name: 'resend',
apiKey: process.env.RESEND_API_KEY,
from: 'Codcompass <hello@news.codcompass.dev>',
},
database: {
tableName: 'subscribers',
referralTable: 'referrals',
},
onboarding: {
enabled: true,
sequence: [
{ delay: '0h', template: 'welcome' },
{ delay: '24h', template: 'best-of' },
{ delay: '72h', template: 'community' },
],
},
referral: {
enabled: true,
reward: {
type: 'access', // 'access' | 'swag' | 'shoutout'
resource: 'premium-articles',
},
trackingDomain: 'ref.codcompass.dev',
},
analytics: {
trackSources: ['github', 'twitter', 'blog', 'discord'],
retentionWindow: 30, // days
},
compliance: {
gdpr: true,
ccpa: true,
unsubscribeHeader: true,
},
};
Quick Start Guide
- Initialize Provider: Sign up for Resend. Add your domain and verify DNS records for SPF/DKIM.
- Deploy Schema: Run the database migration to create
subscribers and referrals tables using the schema provided in the Core Solution.
- Add Signup Endpoint: Create an API route
/api/subscribe that accepts email and ref parameters and calls ReferralService.processSubscription.
- Embed Form: Add a simple HTML form to your site that posts to
/api/subscribe. Include a script to capture URL ?ref= parameters.
- Verify Flow: Submit a test subscription. Check the database for the new record and verify the welcome email arrives within 60 seconds.
This architecture transforms newsletter growth from a manual marketing task into a scalable engineering system. By implementing referral loops, automated onboarding, and rigorous analytics, you build a self-reinforcing growth engine that compounds your technical authority and audience reach.