개요#
정적 사이트 생성기(Hugo)로 만든 블로그에 구독 및 이메일 알림 기능을 추가하는 방법을 분석합니다. 특히 키워드 기반 선택적 알림 기능 구현까지 다룹니다.
1. RSS Feed + 이메일 서비스#
개념#
Hugo의 기본 RSS Feed를 이메일로 변환하는 서비스를 활용하는 방식입니다.
방법 A: Blogtrottr#
동작 방식#
1. Hugo가 자동 생성한 RSS Feed (index.xml)
↓
2. 사용자가 Blogtrottr에 RSS URL 등록
↓
3. Blogtrottr가 주기적으로 RSS 확인
↓
4. 새 글 감지 시 이메일 발송
장점#
- ✅ 개발자 작업 없음 (링크만 제공)
- ✅ 완전 무료
- ✅ 즉시 사용 가능
- ✅ 서버 없이 동작
단점#
- ❌ 구독자 관리 불가
- ❌ 이메일 디자인 커스텀 불가
- ❌ 통계 없음
- ❌ 키워드 필터링 불가
- ❌ 사용자가 직접 외부 사이트에서 등록해야 함
구현 난이도#
⭐ (1/5) - 가장 쉬움
사용 예시#
블로그에 링크 추가:
[이메일로 구독하기](https://blogtrottr.com)
(사이트에서 https://0andwild.github.io/index.xml 입력)
방법 B: FeedBurner (Google)#
동작 방식#
1. FeedBurner에 RSS Feed 등록
↓
2. FeedBurner가 RSS를 프록시/관리
↓
3. 구독 폼을 블로그에 삽입
↓
4. 사용자가 블로그에서 직접 구독
↓
5. 새 글 발행 시 자동 이메일 발송
장점#
- ✅ 기본 통계 제공
- ✅ 구독 폼 제공
- ✅ 무료
- ✅ RSS 관리 기능
단점#
- ❌ Google의 지원 중단 가능성 (업데이트 중단됨)
- ❌ 키워드 필터링 불가
- ❌ 커스텀 제한적
- ❌ 오래된 UI
구현 난이도#
⭐⭐ (2/5)
2. Mailchimp + RSS Campaign (추천)#
개념#
전문 이메일 마케팅 플랫폼을 활용하여 RSS Feed를 자동으로 이메일로 변환
동작 방식#
1. Mailchimp에 RSS Campaign 생성
↓
2. RSS URL 등록 및 체크 주기 설정 (일/주/월)
↓
3. 블로그에 Mailchimp 구독 폼 삽입
↓
4. 사용자가 이메일 입력하여 구독
↓
5. 새 글 감지 시 자동으로 이메일 템플릿 생성
↓
6. 전체 구독자에게 발송
장점#
- ✅ 무료 티어: 2,000명 구독자까지
- ✅ 전문적인 이메일 디자인 (드래그 앤 드롭 에디터)
- ✅ 구독자 관리 (추가/삭제/세그먼트)
- ✅ 상세한 통계 (오픈율, 클릭율, 구독 해지율)
- ✅ 구독 폼 자동 생성 (임베드 코드 제공)
- ✅ 자동화 (새 글만 발송)
- ✅ 모바일 최적화
- ✅ 스팸 필터 회피 (전문 발송 서버)
단점#
- ❌ 키워드 필터링 기본 미지원 (Pro 플랜에서 태그별 세그먼트 가능)
- ❌ 무료 티어에서 Mailchimp 로고 표시
- ❌ 2,000명 초과 시 유료 ($13/월~)
구현 난이도#
⭐⭐ (2/5)
설정 단계#
1. Mailchimp 계정 생성
2. Audience 생성
3. Campaign → Create → Email → RSS Campaign
4. RSS URL 입력: https://your-blog.com/index.xml
5. 발송 주기 설정 (Daily/Weekly)
6. 이메일 템플릿 디자인
7. 구독 폼 코드 복사
8. Hugo에 삽입 (layouts/partials/subscribe.html)
블로그 삽입 코드 예시#
<!-- Mailchimp 구독 폼 -->
<div id="mc_embed_signup">
<form action="https://your-mailchimp-url.com/subscribe" method="post">
<input type="email" name="EMAIL" placeholder="이메일 주소" required>
<button type="submit">구독하기</button>
</form>
</div>
3. Buttondown (개발자 친화적, 추천)#
개념#
Markdown 기반의 뉴스레터 플랫폼으로, API를 통한 커스터마이징이 가능
동작 방식#
1. Buttondown에 RSS Feed 연동
↓
2. 자동으로 RSS 항목을 Markdown 이메일로 변환
↓
3. 구독자가 태그/키워드 선택 가능
↓
4. API를 통해 특정 태그 구독자만 필터링 가능
↓
5. 매칭되는 구독자에게만 발송
장점#
- ✅ 무료 티어: 1,000명까지
- ✅ Markdown 기반 (개발자 친화적)
- ✅ 강력한 API (커스텀 가능)
- ✅ 태그 기반 구독 (키워드 필터링 구현 가능)
- ✅ 광고 없음
- ✅ 깔끔한 UI
- ✅ RSS import 자동화
- ✅ 프라이버시 중심
단점#
- ❌ 이메일 디자인이 단순 (Markdown만)
- ❌ 통계 기능이 Mailchimp보다 약함
- ❌ 한국어 지원 부족
구현 난이도#
⭐⭐⭐ (3/5) - API 사용 시 난이도 증가
키워드 알림 구현 예시#
1단계: 구독 폼에 태그 선택 추가#
<form action="https://buttondown.email/api/emails/embed-subscribe/YOUR_ID" method="post">
<input type="email" name="email" placeholder="이메일" required>
<label>관심 주제 선택:</label>
<input type="checkbox" name="tags" value="kubernetes"> Kubernetes
<input type="checkbox" name="tags" value="docker"> Docker
<input type="checkbox" name="tags" value="golang"> Go
<button type="submit">구독하기</button>
</form>
2단계: GitHub Actions로 선택적 발송#
name: Send Newsletter
on:
push:
paths:
- 'content/posts/**'
jobs:
send:
runs-on: ubuntu-latest
steps:
- name: Extract tags from post
run: |
TAGS=$(grep "^tags = " content/posts/*/index.md | cut -d'"' -f2)
echo "POST_TAGS=$TAGS" >> $GITHUB_ENV
- name: Send to matching subscribers
run: |
curl -X POST https://api.buttondown.email/v1/emails \
-H "Authorization: Token ${{ secrets.BUTTONDOWN_API_KEY }}" \
-d "subject=New Post" \
-d "body=..." \
-d "tag=$POST_TAGS"
4. SendGrid + GitHub Actions (완전 커스텀)#
개념#
이메일 발송 API와 CI/CD를 결합하여 완전히 커스터마이징된 알림 시스템 구축
동작 방식#
1. 새 글 작성 후 Git Push
↓
2. GitHub Actions 트리거
↓
3. Action에서 Front Matter 파싱
- 글 제목, 요약, 태그 추출
↓
4. 구독자 DB 조회 (Supabase/JSON 파일)
- 각 구독자의 관심 키워드와 매칭
↓
5. 매칭되는 구독자만 필터링
↓
6. SendGrid API로 개별 이메일 발송
장점#
- ✅ 완전한 통제 (모든 로직 커스터마이징)
- ✅ 키워드 알림 완벽 구현
- ✅ 무료 티어: SendGrid 월 100통
- ✅ 자동화 (Git push만 하면 됨)
- ✅ 확장 가능 (DB, 로직 자유롭게)
- ✅ 구독자 데이터 소유
단점#
- ❌ 개발 작업 필요
- ❌ 유지보수 부담
- ❌ SendGrid 무료 티어 제한적 (월 100통)
- ❌ 구독 폼, DB 직접 구현 필요
- ❌ 스팸 필터 회피 설정 필요
구현 난이도#
⭐⭐⭐⭐⭐ (5/5) - 가장 복잡
아키텍처#
구독자 데이터베이스 옵션#
옵션 A: JSON 파일 (간단)
// subscribers.json (GitHub 저장소에 암호화하여 저장)
[
{
"email": "user@example.com",
"keywords": ["kubernetes", "docker"],
"active": true
},
{
"email": "dev@example.com",
"keywords": ["golang", "rust"],
"active": true
}
]
옵션 B: Supabase (권장)
-- subscribers 테이블
CREATE TABLE subscribers (
id UUID PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
keywords TEXT[], -- 배열 형태
active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW()
);
GitHub Actions 워크플로우#
name: Email Notification
on:
push:
branches: [main]
paths:
- 'content/posts/**'
jobs:
notify:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Extract Post Metadata
id: metadata
run: |
# 가장 최근 수정된 포스트 찾기
POST_FILE=$(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} | grep 'content/posts' | head -1)
# Front Matter 파싱
TITLE=$(grep "^title = " $POST_FILE | cut -d'"' -f2)
TAGS=$(grep "^tags = " $POST_FILE | sed 's/tags = \[//;s/\]//;s/"//g')
SUMMARY=$(grep "^summary = " $POST_FILE | cut -d'"' -f2)
URL="https://0andwild.github.io/$(dirname $POST_FILE | sed 's/content\///')"
echo "title=$TITLE" >> $GITHUB_OUTPUT
echo "tags=$TAGS" >> $GITHUB_OUTPUT
echo "summary=$SUMMARY" >> $GITHUB_OUTPUT
echo "url=$URL" >> $GITHUB_OUTPUT
- name: Query Matching Subscribers
id: subscribers
run: |
# Supabase에서 매칭되는 구독자 조회
curl -X POST https://YOUR_PROJECT.supabase.co/rest/v1/rpc/get_matching_subscribers \
-H "apikey: ${{ secrets.SUPABASE_KEY }}" \
-H "Content-Type: application/json" \
-d "{\"post_tags\": \"${{ steps.metadata.outputs.tags }}\"}" \
> subscribers.json
- name: Send Emails via SendGrid
run: |
# Node.js 스크립트 실행
cat > send-emails.js << 'EOF'
const sgMail = require('@sendgrid/mail');
const fs = require('fs');
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const subscribers = JSON.parse(fs.readFileSync('subscribers.json'));
const title = process.env.POST_TITLE;
const summary = process.env.POST_SUMMARY;
const url = process.env.POST_URL;
subscribers.forEach(async (subscriber) => {
const msg = {
to: subscriber.email,
from: 'noreply@0andwild.github.io',
subject: `새 글: ${title}`,
html: `
<h2>${title}</h2>
<p>${summary}</p>
<p>관심 키워드와 일치: ${subscriber.matched_keywords.join(', ')}</p>
<a href="${url}">글 읽기</a>
<hr>
<small><a href="https://0andwild.github.io/unsubscribe?token=${subscriber.token}">구독 취소</a></small>
`
};
await sgMail.send(msg);
console.log(`Email sent to ${subscriber.email}`);
});
EOF
npm install @sendgrid/mail
node send-emails.js
env:
SENDGRID_API_KEY: ${{ secrets.SENDGRID_API_KEY }}
POST_TITLE: ${{ steps.metadata.outputs.title }}
POST_SUMMARY: ${{ steps.metadata.outputs.summary }}
POST_URL: ${{ steps.metadata.outputs.url }}
구독 폼 구현 (Hugo Shortcode)#
<!-- layouts/shortcodes/subscribe.html -->
<div class="subscription-form">
<h3>블로그 구독하기</h3>
<form id="subscribe-form">
<input type="email" id="email" placeholder="이메일 주소" required>
<fieldset>
<legend>관심 주제 선택 (선택한 주제의 글만 알림)</legend>
<label><input type="checkbox" name="keywords" value="kubernetes"> Kubernetes</label>
<label><input type="checkbox" name="keywords" value="docker"> Docker</label>
<label><input type="checkbox" name="keywords" value="golang"> Go</label>
<label><input type="checkbox" name="keywords" value="rust"> Rust</label>
<label><input type="checkbox" name="keywords" value="devops"> DevOps</label>
</fieldset>
<button type="submit">구독하기</button>
</form>
<script>
document.getElementById('subscribe-form').addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.getElementById('email').value;
const keywords = Array.from(document.querySelectorAll('input[name="keywords"]:checked'))
.map(cb => cb.value);
// Supabase에 저장
const response = await fetch('https://YOUR_PROJECT.supabase.co/rest/v1/subscribers', {
method: 'POST',
headers: {
'apikey': 'YOUR_ANON_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, keywords, active: true })
});
if (response.ok) {
alert('구독이 완료되었습니다!');
} else {
alert('오류가 발생했습니다.');
}
});
</script>
</div>
Supabase 함수 (키워드 매칭)#
-- 매칭되는 구독자를 찾는 함수
CREATE OR REPLACE FUNCTION get_matching_subscribers(post_tags TEXT)
RETURNS TABLE(email TEXT, matched_keywords TEXT[], token TEXT) AS $$
BEGIN
RETURN QUERY
SELECT
s.email,
ARRAY(
SELECT unnest(s.keywords)
INTERSECT
SELECT unnest(string_to_array(post_tags, ','))
) as matched_keywords,
s.unsubscribe_token as token
FROM subscribers s
WHERE s.active = true
AND s.keywords && string_to_array(post_tags, ',') -- 배열 겹침 연산자
;
END;
$$ LANGUAGE plpgsql;
비용 분석#
- SendGrid: 월 100통 무료 (이후 $19.95/월)
- Supabase: 월 500MB DB, 2GB 전송 무료
- GitHub Actions: 월 2,000분 무료
- 총 비용: 완전 무료 (소규모 블로그)
5. 완전 커스텀 (Supabase + GitHub Actions + Resend)#
SendGrid 대안: Resend#
SendGrid보다 개발자 친화적인 최신 이메일 API
장점#
- ✅ 무료 티어: 월 3,000통 (SendGrid의 30배!)
- ✅ 더 간단한 API
- ✅ React Email 지원 (JSX로 이메일 작성)
- ✅ 더 나은 개발자 경험
Resend 사용 예시#
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
await resend.emails.send({
from: 'blog@0andwild.github.io',
to: subscriber.email,
subject: `새 글: ${title}`,
html: `<p>${summary}</p><a href="${url}">읽기</a>`
});
비교표#
| 방법 | 무료 한도 | 키워드 알림 | 난이도 | 구독자 관리 | 커스텀 | 추천 |
|---|---|---|---|---|---|---|
| Blogtrottr | 무제한 | ❌ | ⭐ | ❌ | ❌ | 테스트용 |
| FeedBurner | 무제한 | ❌ | ⭐⭐ | ⚠️ | ⚠️ | 비추천 (지원 중단) |
| Mailchimp | 2,000명 | ⚠️ (Pro) | ⭐⭐ | ✅ | ⚠️ | 일반 구독용 |
| Buttondown | 1,000명 | ✅ | ⭐⭐⭐ | ✅ | ✅ | 개발자용 |
| SendGrid + Actions | 100통/월 | ✅ | ⭐⭐⭐⭐⭐ | ✅ | ✅✅ | 고급 사용자 |
| Resend + Actions | 3,000통/월 | ✅ | ⭐⭐⭐⭐⭐ | ✅ | ✅✅ | 완벽한 통제 |
추천 로드맵#
단계 1: 빠른 시작 (즉시)#
Mailchimp RSS Campaign
- 10분 설정
- 전체 구독자에게 모든 글 알림
단계 2: 개선 (1주 후)#
Buttondown으로 마이그레이션
- 더 깔끔한 경험
- 기본 태그 기능
단계 3: 고급 기능 (필요 시)#
Resend + GitHub Actions + Supabase
- 키워드 기반 선택적 알림
- 완전한 통제
- 확장 가능성
결론#
일반 블로거라면:#
→ Mailchimp (가장 쉽고 전문적)
개발자 블로그라면:#
→ Buttondown (개발자 친화적, API 제공)
키워드 알림이 필수라면:#
→ Resend + GitHub Actions + Supabase (완전 커스텀)
돈 안 쓰고 테스트하려면:#
→ Blogtrottr (30초 설정)
빠른시작#
실제 구현을 원하신다면:
- Mailchimp로 시작 (학습 곡선 낮음)
- 트래픽 증가 시 Buttondown 고려
- 고급 기능 필요 시 커스텀 솔루션 구축
키워드 알림은 초기엔 과한 기능일 수 있으니, 기본 구독부터 시작하는 것을 권장합니다.



