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
| Method | Free Limit | Keyword Alerts | Difficulty | Subscriber Mgmt | Custom | Recommend |
|---|---|---|---|---|---|---|
| Blogtrottr | Unlimited | ❌ | ⭐ | ❌ | ❌ | Testing only |
| FeedBurner | Unlimited | ❌ | ⭐⭐ | ⚠️ | ⚠️ | Not recommended (discontinued) |
| Mailchimp | 2,000 | ⚠️ (Pro) | ⭐⭐ | ✅ | ⚠️ | General subscriptions |
| Buttondown | 1,000 | ✅ | ⭐⭐⭐ | ✅ | ✅ | For developers |
| SendGrid + Actions | 100/month | ✅ | ⭐⭐⭐⭐⭐ | ✅ | ✅✅ | Advanced users |
| Resend + Actions | 3,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:
- Start with Mailchimp (low learning curve)
- Consider Buttondown when traffic grows
- Build custom solution when advanced features needed
Keyword alerts may be overkill initially, so it’s recommended to start with basic subscriptions.
