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.



