FeaturesTemplatesShowcaseTownie
AI
BlogDocsPricing
Log inSign up
stevekrouse
stevekrousepostherous
Remix of paulkinlan/postherous
Public
Like
postherous
Home
Code
22
backend
3
frontend
2
shared
2
.vtignore
ACTIVITYPUB-STATUS.md
ACTIVITYPUB-TROUBLESHOOTING.md
ACTIVITYPUB.md
README.md
SECURITY.md
SETUP.md
H
debug-config.ts
H
debug-signatures.ts
deno.json
E
email.ts
H
generate-keys.ts
H
test-activitypub-delivery.ts
H
test-activitypub-inbox.ts
H
test-activitypub.ts
H
test-follow.ts
H
test-http-signatures.ts
H
test-publish.ts
H
test-verification.ts
Branches
1
Pull requests
Remixes
History
Environment variables
8
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
/
Code
/
Search
https://stevekrouse--b5e4ed8d183a44d6bd8c1d2576ad0be0.web.val.run
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
  • ๐Ÿ–ผ๏ธ Image Storage: Upload images via web interface or email attachments with automatic processing
  • ๐Ÿ“ก 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 โœ…
    • Content negotiation for HTML/JSON responses
    • Actor profile accessible from browsers and ActivityPub clients
  • ๐Ÿฆ‹ 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 for email publishing)
  • BASE_URL - Your blog's base URL (e.g., https://myblog.com or just myblog.com) - Required for custom domains and ActivityPub federation
  • UPLOAD_PASSWORD - Password required for image uploads (required to enable image upload functionality)

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
  • ADMIN_PASSWORD - Admin password for advanced features (falls back to UPLOAD_PASSWORD if not set)

Environment Variables (Required for ActivityPub HTTP Signatures)

For proper ActivityPub federation with HTTP signatures (recommended for production):

  • ACTIVITYPUB_PUBLIC_KEY - RSA public key in PEM format
  • ACTIVITYPUB_PRIVATE_KEY - RSA private key in PEM format

To generate these keys:

  1. Visit /generate-keys.ts (your key generator val)
  2. Copy the generated keys to your environment variables
  3. Restart your val

Without these keys, the system will generate temporary keys that change on restart, which may cause federation issues.

๐Ÿงช Development Mode & Testing

The platform includes several testing and debugging endpoints that are only accessible in development mode for security.

Enabling Development Mode

Set one of these environment variables to enable testing endpoints:

  • NODE_ENV=development
  • DEV_MODE=true
  • ENABLE_TEST_ENDPOINTS=true

Available Test Endpoints

When development mode is enabled, you can access:

  • /test-activitypub.ts - Test ActivityPub and WebFinger endpoints
  • /test-publish.ts - Create a test blog post
  • /test-follow.ts - Test Follow activity processing
  • /test-verification.ts - Test email verification flow
  • /test-http-signatures.ts - Test HTTP signatures implementation
  • /test-activitypub-inbox.ts - Test ActivityPub inbox functionality
  • /test-activitypub-delivery.ts - Test ActivityPub delivery with HTTP signatures
  • /debug-config.ts - Debug email security configuration
  • /debug-signatures.ts - Debug HTTP signatures in detail
  • /generate-keys.ts - Generate RSA keys for ActivityPub

Production Security

In production (when development mode is disabled), all test endpoints return a 404 response, ensuring your production environment remains secure while allowing full testing capabilities during development.

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 /upload - Image upload interface โœ…
  • GET /images/:filename - Serve uploaded images โœ…
  • GET /rss - RSS 2.0 feed
  • GET /api/posts - JSON API for posts
  • GET /api/posts/:slug - JSON API for single post
  • POST /api/images/upload - Upload image endpoint โœ…
  • GET /api/posts/:slug/images - Get images for a specific post โœ…
  • GET /api/images/user/:email - Get images uploaded by a user โœ…
  • GET /api/images - Get all images (admin endpoint) โœ…
  • DELETE /api/images/:filename - Delete an image โœ…
  • 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

๐Ÿ–ผ๏ธ Image Storage & Management

The platform includes comprehensive image storage capabilities for both static assets and email attachments.

Image Upload Methods

  1. Web Interface: Visit /upload (requires UPLOAD_PASSWORD) to upload images via drag-and-drop interface
  2. Email Attachments: Images attached to blog post emails are automatically processed and stored

Supported Image Formats

  • JPEG/JPG
  • PNG
  • GIF
  • WebP
  • SVG

File Size Limit: 5MB per image

Image Storage Features

  • Password Protection: Upload interface protected by UPLOAD_PASSWORD environment variable
  • Automatic Processing: Email attachments are automatically extracted and stored
  • Content Reference Replacement: Image references in email content are automatically updated to use stored URLs
  • Metadata Storage: Original filename, MIME type, file size, alt text, and post associations
  • Secure Access: Password-protected upload system prevents unauthorized access
  • Blob Storage: Images stored using Val Town's blob storage with unique filenames
  • URL Generation: Clean /images/filename URLs for serving images

Image API Usage

// Upload an image (requires password) const formData = new FormData(); formData.append('image', file); formData.append('password', 'your-upload-password'); formData.append('email', 'your-email@example.com'); formData.append('altText', 'Description of image'); formData.append('postSlug', 'my-blog-post'); // optional const response = await fetch('/api/images/upload', { method: 'POST', body: formData }); // Get images for a post (no password required) const images = await fetch('/api/posts/my-post-slug/images').then(r => r.json()); // Get images by user (requires password) const userImages = await fetch('/api/images/user/user@example.com?password=your-upload-password').then(r => r.json()); // Get all images (requires password) const allImages = await fetch('/api/images?password=your-upload-password&limit=50').then(r => r.json());

Email Attachment Processing

When you send an email with image attachments:

  1. Automatic Detection: System identifies image attachments by MIME type
  2. Storage: Images are stored with unique filenames in blob storage
  3. Database Records: Metadata is saved linking images to the post
  4. Content Updates: Email content is updated to reference stored image URLs
  5. Reference Replacement: Common patterns like cid:image.jpg are replaced with proper URLs

Example email with attachment:

To: your-blog@val.town
From: author@example.com
Subject: My Post with Images
Attachments: photo.jpg, diagram.png

<p>Check out this photo:</p>
<img src="cid:photo.jpg" alt="My photo" />
<p>And this diagram: [diagram.png]</p>

After processing, the content becomes:

<p>Check out this photo:</p> <img src="/images/1234567890-abc123-photo.jpg" alt="My photo" /> <p>And this diagram: ![diagram.png](/images/1234567890-def456-diagram.png)</p>

๐Ÿงช 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! ๐Ÿ“งโœจ

HTTP
  • index.ts
    stevekrouse--5eโ€ฆ8d.web.val.run
  • debug-config.ts
    stevekrouse--b5โ€ฆe0.web.val.run
  • debug-signatures.ts
    stevekrouse--8dโ€ฆ5e.web.val.run
  • generate-keys.ts
    stevekrouse--97โ€ฆ5c.web.val.run
  • test-activitypub-delivery.ts
    stevekrouse--93โ€ฆe3.web.val.run
  • test-activitypub-inbox.ts
    stevekrouse--51โ€ฆ5a.web.val.run
  • test-activitypub.ts
    stevekrouse--28โ€ฆ02.web.val.run
  • test-follow.ts
    stevekrouse--1cโ€ฆ65.web.val.run
  • test-http-signatures.ts
    stevekrouse--65โ€ฆ0c.web.val.run
  • test-publish.ts
    stevekrouse--73โ€ฆ14.web.val.run
  • test-verification.ts
    stevekrouse--f1โ€ฆ6d.web.val.run
Email
  • email.ts
Code
backendfrontendshared.vtignoreACTIVITYPUB-STATUS.mdACTIVITYPUB-TROUBLESHOOTING.mdACTIVITYPUB.mdREADME.mdSECURITY.mdSETUP.md
H
debug-config.ts
H
debug-signatures.ts
deno.json
E
email.ts
H
generate-keys.ts
H
test-activitypub-delivery.ts
H
test-activitypub-inbox.ts
H
test-activitypub.ts
H
test-follow.ts
H
test-http-signatures.ts
H
test-publish.ts
H
test-verification.ts
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.