Skip to main content
Complete Guide to Blog Subscription and Email Notification Systems

Complete Guide to Blog Subscription and Email Notification Systems

· loading · loading ·
gunyoung.Park
Author
gunyoung.Park
Always curious, always exploring new tech
Table of Contents
Hugo - This article is part of a series.
Part 5: This Article

Overview
#

This guide analyzes methods to add subscription and email notification features to blogs built with static site generators (Hugo). We’ll cover everything from basic subscriptions to keyword-based selective notifications.


1. RSS Feed + Email Services
#

Concept
#

Leverage services that convert Hugo’s built-in RSS Feed into email notifications.

Method A: Blogtrottr
#

How It Works
#

1. Hugo automatically generates RSS Feed (index.xml)
   ↓
2. Users register RSS URL on Blogtrottr
   ↓
3. Blogtrottr periodically checks RSS
   ↓
4. Sends email when new posts detected

Advantages
#

  • βœ… No developer work (just provide link)
  • βœ… Completely free
  • βœ… Works immediately
  • βœ… No server required

Disadvantages
#

  • ❌ No subscriber management
  • ❌ No email design customization
  • ❌ No analytics
  • ❌ No keyword filtering
  • ❌ Users must register on external site

Implementation Difficulty
#

⭐ (1/5) - Easiest

Usage Example
#

Add link to blog:
[Subscribe via Email](https://blogtrottr.com)
(Enter https://0andwild.github.io/index.xml on the site)

Method B: FeedBurner (Google)
#

How It Works
#

1. Register RSS Feed with FeedBurner
   ↓
2. FeedBurner proxies/manages RSS
   ↓
3. Embed subscription form on blog
   ↓
4. Users subscribe directly from blog
   ↓
5. Auto-sends email when new posts published

Advantages
#

  • βœ… Basic analytics provided
  • βœ… Subscription form provided
  • βœ… Free
  • βœ… RSS management features

Disadvantages
#

  • ❌ Google may discontinue support (updates stopped)
  • ❌ No keyword filtering
  • ❌ Limited customization
  • ❌ Outdated UI

Implementation Difficulty
#

⭐⭐ (2/5)


2. Mailchimp + RSS Campaign (Recommended)#

Concept
#

Leverage professional email marketing platform to automatically convert RSS Feed to emails

How It Works
#

1. Create RSS Campaign in Mailchimp
   ↓
2. Register RSS URL and set check frequency (daily/weekly/monthly)
   ↓
3. Embed Mailchimp subscription form on blog
   ↓
4. Users enter email to subscribe
   ↓
5. Auto-generates email template when new post detected
   ↓
6. Sends to all subscribers

Advantages
#

  • βœ… Free tier: Up to 2,000 subscribers
  • βœ… Professional email design (drag-and-drop editor)
  • βœ… Subscriber management (add/delete/segment)
  • βœ… Detailed analytics (open rate, click rate, unsubscribe rate)
  • βœ… Auto-generated subscription forms (embed code provided)
  • βœ… Automation (only sends on new posts)
  • βœ… Mobile optimized
  • βœ… Spam filter avoidance (professional sending servers)

Disadvantages
#

  • ❌ No keyword filtering by default (tag-based segmentation on Pro plan)
  • ❌ Mailchimp logo shown on free tier
  • ❌ Paid after 2,000 subscribers ($13/month+)

Implementation Difficulty
#

⭐⭐ (2/5)

Setup Steps
#

1. Create Mailchimp account
2. Create Audience
3. Campaign β†’ Create β†’ Email β†’ RSS Campaign
4. Enter RSS URL: https://your-blog.com/index.xml
5. Set sending frequency (Daily/Weekly)
6. Design email template
7. Copy subscription form code
8. Insert in Hugo (layouts/partials/subscribe.html)

Blog Embed Code Example
#

<!-- Mailchimp subscription form -->
<div id="mc_embed_signup">
  <form action="https://your-mailchimp-url.com/subscribe" method="post">
    <input type="email" name="EMAIL" placeholder="Email address" required>
    <button type="submit">Subscribe</button>
  </form>
</div>

3. Buttondown (Developer-Friendly, Recommended)#

Concept
#

Markdown-based newsletter platform with API for customization

How It Works
#

1. Connect RSS Feed to Buttondown
   ↓
2. Auto-converts RSS items to Markdown emails
   ↓
3. Subscribers can select tags/keywords
   ↓
4. Filter subscribers by specific tags via API
   ↓
5. Send only to matching subscribers

Advantages
#

  • βœ… Free tier: Up to 1,000 subscribers
  • βœ… Markdown-based (developer-friendly)
  • βœ… Powerful API (customizable)
  • βœ… Tag-based subscriptions (keyword filtering possible)
  • βœ… No ads
  • βœ… Clean UI
  • βœ… RSS import automation
  • βœ… Privacy-focused

Disadvantages
#

  • ❌ Simple email design (Markdown only)
  • ❌ Analytics weaker than Mailchimp
  • ❌ Limited Korean support

Implementation Difficulty
#

⭐⭐⭐ (3/5) - Increases with API usage

Keyword Notification Example
#

Step 1: Add tag selection to subscription form
#

<form action="https://buttondown.email/api/emails/embed-subscribe/YOUR_ID" method="post">
  <input type="email" name="email" placeholder="Email" required>

  <label>Select topics of interest:</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">Subscribe</button>
</form>

Step 2: Selective sending via 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 (Fully Custom)
#

Concept
#

Build fully customized notification system combining email sending API with CI/CD

How It Works
#

1. Write new post and Git Push
   ↓
2. GitHub Actions triggered
   ↓
3. Action parses Front Matter
   - Extract title, summary, tags
   ↓
4. Query subscriber DB (Supabase/JSON file)
   - Match each subscriber's keywords
   ↓
5. Filter matching subscribers only
   ↓
6. Send individual emails via SendGrid API

Advantages
#

  • βœ… Complete control (customize all logic)
  • βœ… Perfect keyword notification implementation
  • βœ… Free tier: SendGrid 100 emails/month
  • βœ… Automation (just git push)
  • βœ… Scalable (DB, logic freely customizable)
  • βœ… Own subscriber data

Disadvantages
#

  • ❌ Development work required
  • ❌ Maintenance burden
  • ❌ SendGrid free tier limited (100 emails/month)
  • ❌ Must implement subscription form and DB yourself
  • ❌ Spam filter avoidance setup needed

Implementation Difficulty
#

⭐⭐⭐⭐⭐ (5/5) - Most complex

Architecture
#

Subscriber Database Options
#

Option A: JSON File (Simple)

// subscribers.json (encrypted in GitHub repository)
[
  {
    "email": "user@example.com",
    "keywords": ["kubernetes", "docker"],
    "active": true
  },
  {
    "email": "dev@example.com",
    "keywords": ["golang", "rust"],
    "active": true
  }
]

Option B: Supabase (Recommended)

-- subscribers table
CREATE TABLE subscribers (
  id UUID PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  keywords TEXT[], -- array type
  active BOOLEAN DEFAULT true,
  created_at TIMESTAMP DEFAULT NOW()
);

GitHub Actions Workflow
#

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: |
          # Find most recently modified post
          POST_FILE=$(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} | grep 'content/posts' | head -1)

          # Parse 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: |
          # Query matching subscribers from 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: |
          # Execute Node.js script
          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: `New Post: ${title}`,
              html: `
                <h2>${title}</h2>
                <p>${summary}</p>
                <p>Matched keywords: ${subscriber.matched_keywords.join(', ')}</p>
                <a href="${url}">Read Post</a>
                <hr>
                <small><a href="https://0andwild.github.io/unsubscribe?token=${subscriber.token}">Unsubscribe</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 }}

Subscription Form Implementation (Hugo Shortcode)
#

<!-- layouts/shortcodes/subscribe.html -->
<div class="subscription-form">
  <h3>Subscribe to Blog</h3>
  <form id="subscribe-form">
    <input type="email" id="email" placeholder="Email address" required>

    <fieldset>
      <legend>Select topics of interest (get notified only for selected topics)</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">Subscribe</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);

      // Save to 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('Successfully subscribed!');
      } else {
        alert('An error occurred.');
      }
    });
  </script>
</div>

Supabase Function (Keyword Matching)
#

-- Function to find matching subscribers
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, ',') -- array overlap operator
  ;
END;
$$ LANGUAGE plpgsql;

Cost Analysis
#

  • SendGrid: 100 emails/month free (then $19.95/month)
  • Supabase: 500MB DB, 2GB transfer free per month
  • GitHub Actions: 2,000 minutes/month free
  • Total cost: Completely free (for small blogs)

5. Fully Custom (Supabase + GitHub Actions + Resend)
#

SendGrid Alternative: Resend
#

More developer-friendly modern email API than SendGrid

Advantages
#

  • βœ… Free tier: 3,000 emails/month (30x more than SendGrid!)
  • βœ… Simpler API
  • βœ… React Email support (write emails in JSX)
  • βœ… Better developer experience

Resend Usage Example
#

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: `New Post: ${title}`,
  html: `<p>${summary}</p><a href="${url}">Read</a>`
});

Comparison Table
#

MethodFree LimitKeyword AlertsDifficultySubscriber MgmtCustomRecommend
BlogtrottrUnlimited❌⭐❌❌Testing only
FeedBurnerUnlimited❌⭐⭐⚠️⚠️Not recommended (discontinued)
Mailchimp2,000⚠️ (Pro)β­β­βœ…βš οΈGeneral subscriptions
Buttondown1,000βœ…β­β­β­βœ…βœ…For developers
SendGrid + Actions100/monthβœ…β­β­β­β­β­βœ…βœ…βœ…Advanced users
Resend + Actions3,000/monthβœ…β­β­β­β­β­βœ…βœ…βœ…Perfect control

Recommended Roadmap#

Stage 1: Quick Start (Immediate)
#

Mailchimp RSS Campaign

  • 10-minute setup
  • All subscribers get all posts

Stage 2: Improvement (After 1 week)
#

Migrate to Buttondown

  • Cleaner experience
  • Basic tag features

Stage 3: Advanced Features (When needed)
#

Resend + GitHub Actions + Supabase

  • Keyword-based selective notifications
  • Complete control
  • Scalability

Conclusion
#

For general bloggers:
#

β†’ Mailchimp (easiest and most professional)

For developer blogs:
#

β†’ Buttondown (developer-friendly, provides API)

If keyword alerts are essential:
#

β†’ Resend + GitHub Actions + Supabase (fully custom)

To test without spending money:
#

β†’ Blogtrottr (30-second setup)


Quick Start
#

If you want actual implementation:

  1. Start with Mailchimp (low learning curve)
  2. Consider Buttondown when traffic grows
  3. Build custom solution when advanced features needed

Keyword alerts may be overkill initially, so it’s recommended to start with basic subscriptions.

Hugo - This article is part of a series.
Part 5: This Article

Related