I Built My Own Mailchimp Alternative in 200 Lines of Code
Source: Dev.to
Architecture Overview
The system consists of three pieces:
- Config file – defines the email sequence.
- Cron job – finds subscribers ready for the next email.
- Queue worker – sends the email.
Config file
{
"workflowSteps": [
{
"step": 1,
"hoursAfterSignup": 24,
"template": {
"subject": "Welcome! 🎉",
"heading": "Hi {{name}}!",
"body": "Thanks for signing up..."
}
},
{ "step": 2, "hoursAfterSignup": 96, ... },
{ "step": 3, "hoursAfterSignup": 264, ... }
]
}
Cron job – find ready subscribers
app.job('*/15 * * * *', async (req, res) => {
const conn = await Datastore.open();
for (const stepConfig of workflowSteps) {
const cutoffTime = new Date(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 });
}
}
});
}
});
Queue worker – send 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();
});
Memory‑efficient processing
Instead of loading all subscribers into memory:
// ❌ Memory issues with 50k+ subscribers
const subscribers = await conn.getMany('subscribers').toArray();
we stream them:
// ✅ Constant memory usage
await conn.getMany('subscribers').forEach(async (sub) => {
await processSubscriber(sub);
});
This scales to 100 k+ subscribers without breaking a sweat.
Preventing duplicate emails
The atomic update ensures only one cron instance can queue a send for a given subscriber/step:
const updated = await conn.updateOne(
'subscribers',
{ _id: subscriber._id, emailsSent: { $nin: [step] } },
{ $push: { emailsSent: step } }
);
if (updated) {
await conn.enqueue('send-email', { subscriberId, step });
}
If sending fails, the worker rolls back the flag so the next cron run retries:
catch (error) {
await conn.updateOne(
'subscribers',
{ _id: subscriberId },
{ $pull: { emailsSent: step } }
);
// Next cron run will retry
}
Deployment & configuration
npm install -g codehooks
coho create my-drip --template drip-email-workflow
coho deploy
Set up your email provider (example: SendGrid):
coho set-env EMAIL_PROVIDER "sendgrid"
coho set-env SENDGRID_API_KEY "SG.your-key"
coho set-env FROM_EMAIL "hello@yourdomain.com"
Your drip campaign is now running.
What Codehooks gives you
- Serverless functions
- NoSQL database (MongoDB‑compatible)
- Cron scheduler
- Queue workers
- Environment secrets
All in one platform—no need to stitch together AWS Lambda, SQS, CloudWatch, EventBridge, etc.
Cost comparison
| Subscribers | Monthly cost (Codehooks + SendGrid) | Annual cost | Mailchimp (Standard) |
|---|---|---|---|
| 5 000 | $19 + $19.95 = $38.95 | $467 | $1 020 |
| 50 000 | $19 + $89.95 = $108.95 | $1 307 | $3 600‑4 800 |
Savings grow with list size.
Template features
- Complete working system
- REST API for subscriber management
- Responsive HTML email templates
- Email audit logging with dry‑run mode
- Integrations: SendGrid, Mailgun, Postmark
- Example configs (onboarding, courses, nurture)
When not to use this
- You need sub‑minute delivery SLAs (use a dedicated transactional service)
- Advanced segmentation UI
- No‑code workflow builder
- Non‑technical team members managing campaigns
This solution is aimed at developers who want control and ownership.
Quick start again
coho create my-campaign --template drip-email-workflow
cd my-campaign
coho deploy
Built this for my own SaaS and thought others might find it useful. No affiliation beyond being a happy user.
Questions? Drop them below!