본문으로 건너뛰기
Hugo & GithubPages 블로그 구독 및 이메일 알림 시스템 구현 가이드

Hugo & GithubPages 블로그 구독 및 이메일 알림 시스템 구현 가이드

· loading · loading ·
gunyoung.Park
작성자
gunyoung.Park
Always curious, always exploring new tech
목차
Hugo - 이 글은 시리즈의 일부입니다.
부분 5: 이 글

개요
#

정적 사이트 생성기(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무제한⭐⭐⚠️⚠️비추천 (지원 중단)
Mailchimp2,000명⚠️ (Pro)⭐⭐⚠️일반 구독용
Buttondown1,000명⭐⭐⭐개발자용
SendGrid + Actions100통/월⭐⭐⭐⭐⭐✅✅고급 사용자
Resend + Actions3,000통/월⭐⭐⭐⭐⭐✅✅완벽한 통제

추천 로드맵
#

단계 1: 빠른 시작 (즉시)
#

Mailchimp RSS Campaign

  • 10분 설정
  • 전체 구독자에게 모든 글 알림

단계 2: 개선 (1주 후)
#

Buttondown으로 마이그레이션

  • 더 깔끔한 경험
  • 기본 태그 기능

단계 3: 고급 기능 (필요 시)
#

Resend + GitHub Actions + Supabase

  • 키워드 기반 선택적 알림
  • 완전한 통제
  • 확장 가능성

결론
#

일반 블로거라면:
#

Mailchimp (가장 쉽고 전문적)

개발자 블로그라면:
#

Buttondown (개발자 친화적, API 제공)

키워드 알림이 필수라면:
#

Resend + GitHub Actions + Supabase (완전 커스텀)

돈 안 쓰고 테스트하려면:
#

Blogtrottr (30초 설정)


빠른시작
#

실제 구현을 원하신다면:

  1. Mailchimp로 시작 (학습 곡선 낮음)
  2. 트래픽 증가 시 Buttondown 고려
  3. 고급 기능 필요 시 커스텀 솔루션 구축

키워드 알림은 초기엔 과한 기능일 수 있으니, 기본 구독부터 시작하는 것을 권장합니다.

Hugo - 이 글은 시리즈의 일부입니다.
부분 5: 이 글

관련 글