A modern blog platform inspired by Posterous that allows publishing posts via email.
Your email blog platform is fully functional! Send an email to start publishing.
index.ts
- Main Hono server with HTML generation and API routes โ
database/
- SQLite schema and query functions โ
services/
- External service integrations โ
ALLOWED_EMAIL_ADDRESSES
environment variable (see Security section)/rss
for syndication/post/[slug]
See SETUP.md for detailed setup instructions and ACTIVITYPUB.md for ActivityPub federation details.
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 federationUPLOAD_PASSWORD
- Password required for image uploads (required to enable image upload functionality)WEBSUB_HUB_URL
- WebSub hub URLACTIVITYPUB_DOMAIN
- Domain for ActivityPub federation (deprecated - use BASE_URL instead)ATPROTO_HANDLE
- AT Protocol handleATPROTO_PASSWORD
- AT Protocol app passwordADMIN_PASSWORD
- Admin password for advanced features (falls back to UPLOAD_PASSWORD if not set)For proper ActivityPub federation with HTTP signatures (recommended for production):
ACTIVITYPUB_PUBLIC_KEY
- RSA public key in PEM formatACTIVITYPUB_PRIVATE_KEY
- RSA private key in PEM formatTo generate these keys:
/generate-keys.ts
(your key generator val)Without these keys, the system will generate temporary keys that change on restart, which may cause federation issues.
The platform includes several testing and debugging endpoints that are only accessible in development mode for security.
Set one of these environment variables to enable testing endpoints:
NODE_ENV=development
DEV_MODE=true
ENABLE_TEST_ENDPOINTS=true
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 ActivityPubIn 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.
To use a custom domain with ActivityPub federation:
BASE_URL=https://yourdomain.com
(with protocol)BASE_URL=yourdomain.com
(protocol will be assumed as https)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.
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 feedGET /api/posts
- JSON API for postsGET /api/posts/:slug
- JSON API for single postPOST /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 endpointGET /.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 checkOnly emails from pre-configured addresses can publish posts. Configure via environment variable:
ALLOWED_EMAIL_ADDRESSES=user1@example.com,user2@example.com,admin@myblog.com
To prevent spoofing, all emails go through a verification process:
The platform includes comprehensive image storage capabilities for both static assets and email attachments.
/upload
(requires UPLOAD_PASSWORD) to upload images via drag-and-drop interfaceFile Size Limit: 5MB per image
/images/filename
URLs for serving images// 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());
When you send an email with image attachments:
cid:image.jpg
are replaced with proper URLsExample 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: </p>
Use /test-publish.ts
to create sample posts for testing.
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:
allowed-user@example.com
/backend/index.ts
getCustomCSS()
functionPosts are automatically syndicated to:
Your blog is now fully federated with ActivityPub! Users can:
Discovery formats:
@blog@your-domain.com
https://your-domain.com/actor
Configuration for custom domains:
BASE_URL=https://your-custom-domain.com
in your environment variablesEnhanced ActivityPub Features:
Real-time interaction tracking:
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! ๐งโจ