Beehiiv Cross-Posting Integration

Automatically cross-post Jekyll blog posts from GitHub Pages to Beehiiv using GitHub Actions.

⚠️ Setup Required

This integration is NOT YET ACTIVE. See ../SETUP.md for complete setup instructions.

Quick Start: You need to:

  1. Request Beehiiv Enterprise API access
  2. Configure GitHub Secrets
  3. Verify API integration
  4. See ../SETUP.md for detailed steps

Overview

This integration enables dual publishing:

  • Primary: GitHub Pages (mrogers.london) - SEO indexed
  • Secondary: Beehiiv web + email - subscribers only (not indexed)

Posts are automatically synced when you push to the main branch.

Prerequisites

1. Request Beehiiv API Access

You need Enterprise-level API access from Beehiiv:

  1. Log into your Beehiiv dashboard
  2. Visit the Help page or use the Chatbot Assistant
  3. Request access to the “Send API” / “Create Post” endpoint
  4. Wait for approval and obtain your API key

Documentation:

2. Configure GitHub Secrets

Add these secrets to your GitHub repository (Settings → Secrets and variables → Actions):

  1. BEEHIIV_API_KEY - Your Beehiiv API key
  2. BEEHIIV_PUBLICATION_ID - Your Beehiiv publication ID

3. Enable “Remove Indexing” in Beehiiv

After the integration is working:

  1. Go to Beehiiv Settings → Publication Settings
  2. Toggle “Disable Indexing” ON
  3. This prevents Google from indexing your Beehiiv site (avoids duplicate content)

Reference: Beehiiv SEO Options

How It Works

Automatic Sync (GitHub Actions)

When you push to the main branch:

  1. GitHub Actions workflow (beehiiv-sync.yml) triggers
  2. Python script (sync_to_beehiiv.py) runs
  3. Script scans _posts/ for new or updated posts
  4. Markdown content is converted to HTML
  5. Posts are created/updated on Beehiiv via API
  6. State file (.beehiiv_sync_state.json) is updated

State Tracking

The .beehiiv_sync_state.json file tracks which posts have been synced:

{
  "synced_posts": {
    "2025-04-13-fat-tails-and-model-collapse": {
      "beehiiv_post_id": "abc123",
      "synced_at": "2025-04-13T10:00:00Z",
      "content_hash": "sha256hash",
      "title": "Fat tails & model collapse"
    }
  },
  "last_sync": "2025-04-13T10:00:00Z"
}

The script uses content hashing to detect when posts are updated.

Local Testing

Test the sync script locally before deploying:

# Set environment variables
export BEEHIIV_API_KEY="your_api_key_here"
export BEEHIIV_PUBLICATION_ID="your_publication_id_here"

# Install dependencies
pip install -r scripts/requirements.txt

# Run sync script
python scripts/sync_to_beehiiv.py

Expected output:

============================================================
Beehiiv Sync Starting
============================================================

Found 5 post(s)
------------------------------------------------------------

Processing: 2025-04-13-fat-tails-and-model-collapse.md
  ⊕ New post, creating...
✓ Post created on Beehiiv: Fat tails & model collapse (ID: abc123)

[... more posts ...]

============================================================
Sync Complete
============================================================
New posts created:     5
Existing posts updated: 0
Posts skipped:         0
Errors:                0
============================================================

Publishing Workflow

New Post

  1. Write post in _posts/YYYY-MM-DD-slug.md
  2. Commit and push to main branch
  3. Automatic:
    • Jekyll builds and deploys to GitHub Pages
    • Beehiiv sync runs and cross-posts
  4. Post appears on:
    • mrogers.london (SEO-indexed)
    • Beehiiv web (subscribers only)
    • Beehiiv email (sent to subscribers)

Update Existing Post

  1. Edit markdown file in _posts/
  2. Commit and push to main
  3. Sync script detects content hash change
  4. Post automatically updates on Beehiiv

Zero manual work after setup!

Manual Trigger

You can manually trigger the sync workflow:

  1. Go to GitHub Actions tab in your repo
  2. Select “Sync to Beehiiv” workflow
  3. Click “Run workflow”
  4. Select the main branch
  5. Click “Run workflow” button

This is useful for:

  • Initial backfill of existing posts
  • Testing after setup
  • Re-syncing if automatic sync fails

Troubleshooting

Workflow Fails: Missing API Key

Error: BEEHIIV_API_KEY environment variable not set

Solution:

  1. Go to GitHub repo Settings → Secrets and variables → Actions
  2. Add BEEHIIV_API_KEY secret with your API key
  3. Re-run the workflow

Workflow Fails: Authentication

Error: Authentication failed. Check BEEHIIV_API_KEY

Solution:

  • Verify your API key is correct
  • Ensure you have Enterprise API access approved
  • Check API key hasn’t expired

Posts Not Appearing on Beehiiv

Possible causes:

  1. API request failed (check workflow logs)
  2. Post is in draft status (should be “confirmed”)
  3. Beehiiv settings prevent publishing

Debug:

  1. Check GitHub Actions logs for errors
  2. Verify state file was updated
  3. Check Beehiiv dashboard for draft posts

Images Not Loading in Beehiiv Emails

Cause: Images are hosted on GitHub Pages

Solution:

  • Ensure GitHub Pages is deployed and accessible
  • Verify image URLs in HTML are absolute (https://mrogers.london/assets/...)
  • Check _site/ directory contains images after build

Rollback

If you need to disable the integration:

  1. Rename the workflow file:
    mv .github/workflows/beehiiv-sync.yml .github/workflows/beehiiv-sync.yml.disabled
    
  2. Commit and push

Your GitHub Pages site continues working normally. Beehiiv posts remain (delete manually if needed).

Files

  • scripts/sync_to_beehiiv.py - Main sync script (~250 lines)
  • scripts/requirements.txt - Python dependencies
  • .github/workflows/beehiiv-sync.yml - GitHub Actions workflow
  • .beehiiv_sync_state.json - Sync state tracking (auto-generated)

Support

For issues with: