Ever looked at your Mailchimp bill and thought "I could build this"?
I did. And I was right.
The Problem
I needed drip emails for my SaaS. Simple 3-step onboarding sequence. Mailchimp wanted $85/month for 5k subscribers. ConvertKit wanted $79/month. ActiveCampaign wanted... let's not talk about it.
For what? Sending time-delayed emails. That's it.
The Solution
Built my own with Codehooks.io (serverless Node.js platform). Total cost: $39/month for infrastructure + email sending. And I own everything.
Here's the entire architecture:
// 1. Config file defines your sequence
{
"workflowSteps": [
{
"step": 1,
"hoursAfterSignup": 24,
"template": {
"subject": "Welcome! π",
"heading": "Hi {{name}}!",
"body": "Thanks for signing up..."
}
},
{ "step": 2, "hoursAfterSignup": 96, ... },
{ "step": 3, "hoursAfterSignup": 264, ... }
]
}
// 2. Cron job finds subscribers ready for next email
app.job('*/15 * * * *', async (req, res) => {
const conn = await Datastore.open();
for (const stepConfig of workflowSteps) {
const cutoffTime = new Date(now - stepConfig.hoursAfterSignup * 60 * 60 * 1000);
// Stream subscribers (constant memory usage)
await conn.getMany('subscribers', {
subscribed: true,
createdAt: { $lte: cutoffTime }
}).forEach(async (subscriber) => {
if (!subscriber.emailsSent?.includes(stepConfig.step)) {
// Atomically mark as sent
const updated = await conn.updateOne(
'subscribers',
{ _id: subscriber._id, emailsSent: { $nin: [stepConfig.step] } },
{ $push: { emailsSent: stepConfig.step } }
);
if (updated) {
await conn.enqueue('send-email', { subscriberId: subscriber._id, step: stepConfig.step });
}
}
});
}
});
// 3. Queue worker sends the email
app.worker('send-email', async (req, res) => {
const { subscriberId, step } = req.body.payload;
const template = await getTemplate(step);
await sendEmail(subscriber.email, template.subject, generateHTML(template));
res.end();
});
That's it. Three pieces:
- Config file - Define your steps
- Cron job - Find subscribers ready for next email
- Queue worker - Send it
Why This Actually Works
Streaming Architecture
Instead of loading all subscribers into memory:
// β Memory issues with 50k+ subscribers
const subscribers = await conn.getMany('subscribers').toArray();
// β
Constant memory usage
await conn.getMany('subscribers').forEach(async (sub) => {
await processSubscriber(sub);
});
Scales to 100k+ subscribers without breaking a sweat.
Race Condition Prevention
The atomic update prevents duplicate emails even if cron jobs overlap:
const updated = await conn.updateOne(
'subscribers',
{ _id: subscriber._id, emailsSent: { $nin: [step] } }, // Only if not already sent
{ $push: { emailsSent: step } }
);
// Only queue if we won the race
if (updated) {
await conn.enqueue('send-email', { subscriberId, step });
}
Automatic Retry
If sending fails, worker removes from sent list:
catch (error) {
await conn.updateOne(
'subscribers',
{ _id: subscriberId },
{ $pull: { emailsSent: step } }
);
// Next cron run will retry
}
Deployment
Literally 3 commands:
npm install -g codehooks
coho create my-drip --template drip-email-workflow
coho deploy
Configure your email provider:
coho set-env EMAIL_PROVIDER "sendgrid"
coho set-env SENDGRID_API_KEY "SG.your-key"
coho set-env FROM_EMAIL "hello@yourdomain.com"
Done. Your drip campaign is running.
The Stack
Codehooks gives you everything in one platform:
- Serverless functions
- NoSQL database (MongoDB-compatible)
- Cron scheduler
- Queue workers
- Environment secrets
No AWS Lambda + SQS + CloudWatch + EventBridge nonsense. Just Node.js and deploy.
Cost Breakdown
For 5,000 subscribers:
- Codehooks Pro: $19/month
- SendGrid Essentials: $19.95/month
- Total: $467/year
vs Mailchimp Standard: $1,020/year
For 50,000 subscribers:
- Codehooks Pro: $19/month
- SendGrid Pro: $89.95/month
- Total: $1,307/year
vs Mailchimp: $3,600-4,800/year
The savings scale with your list size.
What You Get
The open source template includes:
β Complete working system
β REST API for subscriber management
β Responsive HTML email templates
β Email audit logging with dry-run mode
β SendGrid, Mailgun, Postmark integrations
β Example configs (onboarding, courses, nurture)
When NOT to Use This
Don't use this if you need:
- Sub-minute delivery SLAs (use transactional email service)
- Advanced segmentation UI
- No-code workflow builder
- Non-technical team members to manage campaigns
This is for developers who want control and ownership.
Try It
coho create my-campaign --template drip-email-workflow
cd my-campaign
coho deploy
Full code: github.com/codehooks-io/codehooks-io-templates
Docs: codehooks.io/docs
Built this for my own SaaS and figured others might find it useful. No affiliation besides being a happy user.
Questions? Drop them below! π
Update Log
2025-01-01: Initial release with streaming architecture and multi-provider support
Top comments (0)