FeaturesTemplatesShowcaseTownie
AI
BlogDocsPricing
Log inSign up
paulkinlan
paulkinlanpostherous
https://posthero.us - an email powered blogging system
Public
Like
6
postherous
Home
Code
16
backend
3
frontend
1
shared
2
ACTIVITYPUB-TROUBLESHOOTING.md
ACTIVITYPUB.md
README.md
SECURITY.md
SETUP.md
H
debug-config.ts
E
email.ts
H
generate-keys.ts
H
test-activitypub-delivery.ts
H
test-activitypub-inbox.ts
H
test-activitypub.ts
H
test-publish.ts
H
test-verification.ts
Branches
1
Pull requests
Remixes
4
History
Environment variables
9
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
README.md
Code
/
README.md
Search
7/11/2025
Viewing readonly version of main branch: v185
View latest version
README.md

Email Blog Platform

A modern blog platform inspired by Posterous that allows publishing posts via email.

🚀 Status: READY TO USE

Your email blog platform is fully functional! Send an email to start publishing.

✨ Features

  • 📧 Email-to-Publish: Send an email to publish posts instantly
  • 🔒 Email Security: Allowlist and verification system to prevent unauthorized posts
  • 🎨 Multi-format Support: HTML and plain text posts
  • 📡 RSS Feed: Full RSS 2.0 support for syndication
  • 🌐 WebSub: Real-time feed updates via WebSub protocol (configured)
  • 🐘 ActivityPub: Full federated social networking with followers, likes, and shares ✅
  • 🦋 AT Protocol: Bluesky integration for cross-platform syndication (configured)
  • 🔗 SEO Friendly: Clean URLs with slugified titles
  • 📱 Responsive Design: Mobile-first responsive interface with TailwindCSS
  • ⚡ Fast: Built on Val Town with SQLite storage and static HTML generation
  • 🚀 No JavaScript: Pure HTML/CSS frontend for maximum performance

🏗️ Architecture

Backend (/backend/)

  • index.ts - Main Hono server with HTML generation and API routes ✅
  • database/ - SQLite schema and query functions ✅
  • services/ - External service integrations ✅

Email Handler (/email.ts)

  • Email trigger handler with security verification ✅
  • Allowlist checking and draft post creation ✅
  • Automated verification email sending ✅

Static HTML Generation

  • Server-side HTML rendering with TailwindCSS ✅
  • No client-side JavaScript dependencies ✅
  • Fast loading and SEO optimized ✅

🎯 Quick Start

  1. Configure Security: Set ALLOWED_EMAIL_ADDRESSES environment variable (see Security section)
  2. Send Email: Send email from an allowed address to your Val Town email address
  3. Verify Email: Click the verification link sent to your email
  4. View Blog: Visit your backend HTTP val URL after verification
  5. RSS Feed: Access /rss for syndication
  6. Individual Posts: Visit /post/[slug]

🔧 Configuration

See SETUP.md for detailed setup instructions and ACTIVITYPUB.md for ActivityPub federation details.

Environment Variables (Required for Security)

  • ALLOWED_EMAIL_ADDRESSES - Comma-separated list of allowed email addresses (required)
  • BASE_URL - Your blog's base URL (e.g., https://myblog.com or just myblog.com) - Required for custom domains and ActivityPub federation

Environment Variables (Optional)

  • WEBSUB_HUB_URL - WebSub hub URL
  • ACTIVITYPUB_DOMAIN - Domain for ActivityPub federation (deprecated - use BASE_URL instead)
  • ATPROTO_HANDLE - AT Protocol handle
  • ATPROTO_PASSWORD - AT Protocol app password

Custom Domain Setup

To use a custom domain with ActivityPub federation:

  1. Set up your custom domain in Val Town (see Val Town documentation)
  2. Set the BASE_URL environment variable to your custom domain:
    • BASE_URL=https://yourdomain.com (with protocol)
    • OR BASE_URL=yourdomain.com (protocol will be assumed as https)
  3. Test ActivityPub discovery by visiting https://yourdomain.com/.well-known/webfinger?resource=acct:blog@yourdomain.com

Without BASE_URL set, the system will use the default Val Town URL for ActivityPub federation.

📊 API Endpoints

  • GET / - Main blog interface (with ActivityPub Link header)
  • GET /post/:slug - Individual post page (supports content negotiation for ActivityPub)
  • GET /rss - RSS 2.0 feed
  • GET /api/posts - JSON API for posts
  • GET /api/posts/:slug - JSON API for single post
  • GET /websub - WebSub subscription endpoint
  • GET /.well-known/webfinger - WebFinger discovery for ActivityPub ✅
  • GET /actor - ActivityPub actor document with rich metadata ✅
  • GET /outbox - ActivityPub outbox (paginated published activities) ✅
  • GET /outbox?page=N - Paginated outbox pages ✅
  • GET /followers - ActivityPub followers collection (with pagination support) ✅
  • GET /followers-list - Human-readable followers page ✅
  • GET /api/followers - JSON API for followers list ✅
  • GET /following - ActivityPub following collection ✅
  • POST /inbox - ActivityPub inbox (processes Follow, Like, Announce, Undo) ✅
  • GET /api/posts/:slug/activities - Get activity counts (likes, shares, replies) ✅
  • GET /verify-email - Email verification endpoint ✅
  • GET /health - Health check

🔒 Security Features

Email Allowlist

Only emails from pre-configured addresses can publish posts. Configure via environment variable:

ALLOWED_EMAIL_ADDRESSES=user1@example.com,user2@example.com,admin@myblog.com

Email Verification

To prevent spoofing, all emails go through a verification process:

  1. Email Received: Email is stored as a draft (not published)
  2. Verification Sent: Automated verification email sent to sender
  3. User Clicks Link: Sender clicks verification link in email
  4. Post Published: After verification, post is published and syndicated

Security Benefits

  • Anti-spoofing: Prevents unauthorized users from publishing posts
  • Email confirmation: Ensures the sender actually sent the email
  • Time-limited: Verification links expire after 24 hours
  • Automatic cleanup: Expired drafts are automatically removed

🧪 Testing

Use /test-publish.ts to create sample posts for testing.

📝 Usage Example

Send an email like this:

To: your-email-val@val.town
From: allowed-user@example.com  (must be in ALLOWED_EMAIL_ADDRESSES)
Subject: My Amazing Blog Post
Body: <h2>Hello World!</h2><p>This post was published via email!</p>

The process will be:

  1. Email received and stored as draft
  2. Verification email sent to allowed-user@example.com
  3. User clicks verification link
  4. Post published with:
    • Title: "My Amazing Blog Post"
    • Slug: "my-amazing-blog-post"
    • Content: Rendered HTML
    • Author: Extracted from email address

🎨 Customization

  • Edit HTML generation functions in /backend/index.ts
  • Modify CSS styles in the getCustomCSS() function
  • Update branding in HTML templates
  • Configure federation services via environment variables

🔄 Syndication

Posts are automatically syndicated to:

  • RSS feed (always enabled)
  • WebSub subscribers (if configured)
  • ActivityPub followers (with full interaction support) ✅
  • AT Protocol/Bluesky (if configured)

ActivityPub Federation

Your blog is now fully federated with ActivityPub! Users can:

  • Follow your blog from Mastodon, Pleroma, and other ActivityPub platforms
  • Like your blog posts (shows ❤️ count on posts)
  • Share/Boost your posts (shows 🔄 count on posts)
  • Reply to your posts (shows 💬 count on posts)
  • Preview posts directly in Mastodon timeline with proper formatting

Discovery formats:

  • WebFinger: @blog@your-domain.com
  • Direct actor URL: https://your-domain.com/actor

Configuration for custom domains:

  • Set BASE_URL=https://your-custom-domain.com in your environment variables
  • This ensures all ActivityPub URLs use your custom domain instead of the default Val Town URL

Enhanced ActivityPub Features:

  • ✅ Paginated Outbox: Posts are served with proper pagination for better performance
  • ✅ Rich Post Previews: Posts display with titles, summaries, and full content in Mastodon
  • ✅ Content Negotiation: Individual posts serve both HTML and ActivityPub JSON based on Accept headers
  • ✅ Profile Metadata: Actor profile includes avatar, header image, and custom fields
  • ✅ Link Discovery: Proper HTTP Link headers for ActivityPub discovery
  • ✅ Post Permalinks: Each post has its own ActivityPub Note endpoint

Real-time interaction tracking:

  • All likes, shares, and replies are stored and displayed on individual post pages
  • Follower count is maintained and accurate
  • Full ActivityPub inbox processing for Follow/Unfollow activities

Example: If your blog is at myblog.com (with BASE_URL=https://myblog.com), users can follow @blog@myblog.com from their Mastodon client and see your posts with rich previews in their timeline!


Ready to blog via email? Send your first post now! 📧✨

Go to top
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Product
FeaturesPricing
Developers
DocsStatusAPI ExamplesNPM Package Examples
Explore
ShowcaseTemplatesNewest ValsTrending ValsNewsletter
Company
AboutBlogCareersBrandhi@val.town
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.