나는 200줄의 코드로 나만의 Mailchimp 대안을 만들었다
Source: Dev.to
(번역할 텍스트가 제공되지 않았습니다. 번역을 원하시는 본문을 알려주시면 한국어로 번역해 드리겠습니다.)
아키텍처 개요
시스템은 세 부분으로 구성됩니다:
- Config file – 이메일 시퀀스를 정의합니다.
- Cron job – 다음 이메일을 받을 준비가 된 구독자를 찾습니다.
- Queue worker – 이메일을 전송합니다.
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 – 준비된 구독자 찾기
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 – 이메일 전송
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();
});
메모리 효율적인 처리
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.
중복 이메일 방지
원자적 업데이트는 특정 구독자/단계에 대해 하나의 크론 인스턴스만 전송을 큐에 넣을 수 있도록 보장합니다:
const updated = await conn.updateOne(
'subscribers',
{ _id: subscriber._id, emailsSent: { $nin: [step] } },
{ $push: { emailsSent: step } }
);
if (updated) {
await conn.enqueue('send-email', { subscriberId, step });
}
전송에 실패하면 워커가 플래그를 롤백하여 다음 크론 실행 시 재시도합니다:
catch (error) {
await conn.updateOne(
'subscribers',
{ _id: subscriberId },
{ $pull: { emailsSent: step } }
);
// Next cron run will retry
}
배포 및 구성
npm install -g codehooks
coho create my-drip --template drip-email-workflow
coho deploy
이메일 제공자를 설정하세요 (예: SendGrid):
coho set-env EMAIL_PROVIDER "sendgrid"
coho set-env SENDGRID_API_KEY "SG.your-key"
coho set-env FROM_EMAIL "hello@yourdomain.com"
이제 드립 캠페인이 실행 중입니다.
Codehooks가 제공하는 것
- Serverless functions
- NoSQL database (MongoDB‑compatible)
- Cron scheduler
- Queue workers
- Environment secrets
하나의 플랫폼에 모두 포함되어 있어 AWS Lambda, SQS, CloudWatch, EventBridge 등을 따로 연결할 필요가 없습니다.
비용 비교
| 구독자 수 | 월 비용 (Codehooks + SendGrid) | 연간 비용 | 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 |
리스트 규모가 커질수록 절감액이 증가합니다.
템플릿 기능
- 완전한 작동 시스템
- 구독자 관리용 REST API
- 반응형 HTML 이메일 템플릿
- 드라이‑런 모드가 있는 이메일 감사 로깅
- 통합: SendGrid, Mailgun, Postmark
- 예시 구성 (온보딩, 코스, 육성)
사용하면 안 되는 경우
- 하위 분 단위 전달 SLA가 필요합니다 (전용 트랜잭션 서비스 사용)
- 고급 세분화 UI
- 코드 없는 워크플로우 빌더
- 비기술적인 팀원이 캠페인을 관리하는 경우
이 솔루션은 제어와 소유권을 원하는 개발자를 위한 것입니다.
빠른 시작 다시
coho create my-campaign --template drip-email-workflow
cd my-campaign
coho deploy
제가 직접 만든 SaaS용 템플릿이며, 다른 사람들에게도 유용할 것이라 생각했습니다. 행복한 사용자일 뿐, 별다른 관계는 없습니다.
질문이 있나요? 아래에 남겨 주세요!