A single-user bridge service that automatically cross-posts from your Bluesky
account to your Mastodon account. Built specifically for
Val.town with TypeScript and Deno.
What it does
Connects your accounts: Securely links your Bluesky and Mastodon accounts
using OAuth
Automatic syncing: Checks for new Bluesky posts every 15 minutes and
cross-posts them to Mastodon
Smart filtering: Only syncs regular posts, not replies, reposts, or posts
that start with mentions, so hopefully only content that makes sense in the
Mastodon context
Smart transformations: Converts Bluesky mentions (@handle.bsky.social) to
profile links since they don't exist on Mastodon
Media support: Uploads images and videos from Bluesky to your Mastodon
instance
Duplicate prevention: Tracks synced posts to avoid posting the same
content twice
Error handling: Retries failed posts and logs errors for troubleshooting
Features
✅ OAuth authentication for both Bluesky and Mastodon
✅ Media cross-posting (images & videos)
✅ Mention transformation (handles → profile links)
✅ Duplicate prevention via content hashing
✅ Retry mechanism with exponential backoff
✅ Error logging and sync tracking
✅ Setup wizard for easy configuration
This serves the setup wizard and handles OAuth callbacks
Cron Trigger (for automatic syncing):
Set cronjob.ts as a Cron val
Schedule: */15 * * * * (every 15 minutes)
For paid accounts, you can use shorter intervals like */5 * * * * (every 5
minutes)
3. Environment Variables
Set these environment variables in your Val.town account:
Required Variables
# Your Val.town deployment URL
VALTOWN_URL=https://your-val-url.web.val.run
# Bluesky App Password for sync service (services do not work with oauth yet on bsky)
ATPROTO_APP_PASSWORD=your-bluesky-app-password
# Security: restrict login to specific handle only
ATPROTO_ALLOWED_HANDLE=your.handle.bsky.social
Create a new App Password (name it something like "Bridge Service")
Copy the generated password and set it as ATPROTO_APP_PASSWORD
Security Configuration
The ATPROTO_ALLOWED_HANDLE variable restricts OAuth setup to only your Bluesky
handle, preventing others from hijacking your single-user bridge service.
4. OAuth Setup
For ATProto/Bluesky
The OAuth client metadata is automatically generated based on your VALTOWN_URL
environment variable. No manual configuration is needed!
The client metadata will be available at:
https://your-val-url.web.val.run/client
For Mastodon
The service automatically registers with your Mastodon instance during setup -
no manual configuration needed.
5. Run the Setup Wizard
Visit your HTTP val URL (e.g., https://your-val-url.web.val.run)
Check the Setup Status: The landing page shows a checklist of what
environment variables need to be configured
Configure missing variables: Set any missing environment variables in
your Val.town settings
Click "Get Started" to begin the setup wizard once all required variables are
set
Connect your Bluesky account by entering your handle
Connect your Mastodon account by entering your instance URL
Complete the setup - that's all!
Usage
Once configured, the service runs automatically:
Every 15 minutes (or your configured interval), the cron job runs
It fetches new posts from your Bluesky account
Transforms the content (mentions become profile links)
Uploads any media to your Mastodon instance
Creates the cross-post on Mastodon
Logs the result for your review
What Gets Cross-Posted
✅ Included:
Regular posts with text
Posts with images/videos
Posts with links and hashtags
Posts with mentions (converted to profile links)
❌ Excluded:
Replies to other posts
Posts that start with mentions (e.g., "@handle.bsky.social blah blah") - these
are typically conversation starters
Empty posts
Posts you've already cross-posted
Re-posts (posts that embed another post)
Dashboard Features
Connection Status: See which accounts are connected
Sync Controls: Enable/disable auto-sync and trigger manual syncs
Recent Activity: View recently synced posts and their status
Error Logs: Track sync failures and troubleshoot issues
Disconnect Options: Disconnect individual accounts if needed
Mastodon compatibility: Works with all Mastodon instances
Single user: Designed for personal use (one Bluesky → one Mastodon)
Troubleshooting
Setup issues: Check the setup status checklist on the landing page for
missing environment variables OAuth failures: Ensure your VALTOWN_URL is publicly accessible (client
metadata is auto-generated at /client) "Unauthorized" errors: Verify ATPROTO_ALLOWED_HANDLE matches your exact
Bluesky handle App Password errors: Generate a new App Password in Bluesky settings and
update ATPROTO_APP_PASSWORD Sync not working: Verify both accounts are connected in the dashboard Missing posts: Check the sync logs for specific error messages Media upload fails: Bluesky/Mastodon may have different file size limits
Privacy & Security
All tokens are stored securely in SQLite
No data leaves your Val.town instance
OAuth follows security best practices (PKCE, DPoP)
Single-user security: ATPROTO_ALLOWED_HANDLE restricts access to your
handle only
App Password option: More reliable than OAuth tokens for automated
services
You can disconnect accounts anytime from the dashboard
Single-user architecture prevents data mixing
Support
This is an open-source project. For issues or feature requests, check the
repository or Val.town community forums.