Automated Premium X/Twitter Content Pipeline
AI News β OpenAI Long-Form Generation β Notion Queue β X Premium Posts
Fully automated premium X/Twitter content system that:
- π‘ Monitors 11 top AI news sources (OpenAI, Google, Microsoft, NVIDIA, Hugging Face, etc.)
- π§ Uses GPT-4o-mini to generate engaging 2,000-5,000 character long-form posts
- ποΈ Stores drafts in Notion with smart duplicate detection
- π Auto-posts to X/Twitter Premium (supports up to 25,000 characters)
- β° Runs daily at 9:00am Sydney time (fully automated via GitHub Actions)
Perfect for: AI enthusiasts, tech influencers, and anyone wanting to share curated AI insights without manual work!
Built by: @skalaliya | License: MIT
graph LR
A[π 11 AI RSS Feeds] --> B[π Score & Rank]
B --> C[π― Select Top Article]
C --> D[π€ OpenAI GPT-4o-mini]
D --> E[π 2-5K Char Long-Form]
E --> F[πΎ Notion Database]
F --> G[β° Wait 5 Minutes]
G --> H[π Post to X/Twitter]
style A fill:#e3f2fd
style D fill:#fff3e0
style F fill:#f3e5f5
style H fill:#e8f5e9
09:00:00 βββββββββββββββββββββββββββββββββββββββ
β π Fetch AI News β
β β’ Parse 11 RSS feeds β
β β’ Score by recency + keywords β
β β’ Check for duplicates β
βββββββββββββββββββ¬ββββββββββββββββββββ
β
09:00:15 βββββββββββββββββββΌββββββββββββββββββββ
β π€ Generate Long-Form Content β
β β’ GPT-4o-mini enrichment β
β β’ 2,000-5,000 character target β
β β’ X-native plain text format β
βββββββββββββββββββ¬ββββββββββββββββββββ
β
09:00:30 βββββββββββββββββββΌββββββββββββββββββββ
β πΎ Save to Notion β
β β’ Status: Scheduled β
β β’ 2000-char chunking for API β
β β’ Store in "Long Form Draft" β
βββββββββββββββββββ¬ββββββββββββββββββββ
β
09:05:00 βββββββββββββββββββΌββββββββββββββββββββ
β π Post to X/Twitter β
β β’ Read from Notion β
β β’ X API v2 (25k char support) β
β β’ Update status to "Posted" β
βββββββββββββββββββββββββββββββββββββββ
- 11 Premium AI Sources: OpenAI, Google AI Blog, Google Research, Microsoft Research, Microsoft AI, Hugging Face, Stability AI, NVIDIA, AWS ML, TechCrunch AI, VentureBeat AI
- Smart Scoring Algorithm:
- β‘ Super fresh (<6h): +15 points
- π₯ Very fresh (6-12h): +12 points
- β Fresh (12-24h): +8 points
- π Keyword matching: +2 per keyword
- π² Source diversity penalties (prevents spam)
- π Similarity detection (prevents duplicates)
- 48-Hour Window: Only considers recent, relevant news
- OpenAI GPT-4o-mini for premium long-form posts
- 2,000-5,000 character target (can go up to 25,000)
- X-native formatting: Plain text with proper spacing (NO markdown symbols)
- Graceful fallback: Uses heuristic summaries if OpenAI unavailable
- Smart Database: Title, Long Form Draft, Status, Scheduled Time, Media URLs
- 2000-char chunking: Handles Notion API rich_text block limits
- Duplicate Detection: Cross-references last 7 days of posts
- Status Tracking: Scheduled β Posted β Failed (with error logs)
- 9:00am Sydney: Fetch & generate content
- 9:05am Sydney: Auto-post to X
- DST-Aware: Automatically handles AEDT (UTC+11) β AEST (UTC+10)
- Dual UTC Crons: Maintains consistent local time year-round
- GitHub Actions: Free (3% of free tier)
- OpenAI API: <$0.01/month (1 call per day)
- Notion API: Free (included in plan)
- X API: Free tier supported
- Total: <$0.01/month π
- β GitHub account (for Actions)
- β Notion account with API access
- β X/Twitter Developer account with API v2 access
- β OpenAI API key (optional but recommended)
Click the "Fork" button at the top of this page to create your own copy.
- Create a new Notion page and add a database with these properties:
| Property Name | Type | Description |
|---|---|---|
Tweet Content |
Title | Short title for table view (200 chars) |
Long Form Draft |
Rich Text | Full long-form content (2-5K chars) |
Status |
Select | Options: Scheduled, Posted, Failed, Skipped |
Scheduled Time |
Date | When to post (auto-set to 5 min ago) |
Media URLs |
Rich Text | Image URLs (optional) |
Error Message |
Rich Text | Error details if Status=Failed |
-
Get your Database ID:
- Open database as full page
- Copy URL:
https://notion.so/{workspace}/{DATABASE_ID}?v=... - Extract the 32-character ID
-
Create Notion Integration:
- Go to https://www.notion.so/my-integrations
- Click "New integration"
- Name it (e.g., "AI Content Scheduler")
- Copy the Internal Integration Token
- Go back to your database and click "β’β’β’" β "Connect to" β Select your integration
-
Apply for X Developer Account:
- Visit https://developer.twitter.com/
- Apply for Elevated access (required for API v2)
-
Create an App:
- Dashboard β Projects & Apps β Create App
- Enable OAuth 1.0a with Read & Write permissions
-
Get Credentials:
API_KEY(Consumer Key)API_KEY_SECRET(Consumer Secret)ACCESS_TOKEN(from "Keys and tokens" tab)ACCESS_TOKEN_SECRET(from "Keys and tokens" tab)
- Visit https://platform.openai.com/api-keys
- Create new secret key
- Copy and save securely (you won't see it again!)
Navigate to your forked repo: Settings β Secrets and variables β Actions β New repository secret
Add these secrets:
| Secret Name | Value | Where to Find |
|---|---|---|
NOTION_TOKEN |
Your Notion integration token | Notion β My Integrations |
NOTION_DB_ID |
Your Notion database ID (32 chars) | Database URL |
ACCESS_TOKEN |
X/Twitter access token | X Developer Portal β Keys and tokens |
ACCESS_TOKEN_SECRET |
X/Twitter access token secret | X Developer Portal β Keys and tokens |
API_KEY |
X/Twitter API key (Consumer Key) | X Developer Portal β Keys and tokens |
API_KEY_SECRET |
X/Twitter API secret (Consumer Secret) | X Developer Portal β Keys and tokens |
OPENAI_API_KEY |
Your OpenAI API key | OpenAI β API Keys |
| Secret Name | Default Value | Description |
|---|---|---|
OPENAI_MODEL |
gpt-4o-mini |
Change to gpt-4 for better quality (higher cost) |
# Clone your fork
git clone https://github.com/YOUR_USERNAME/notion-x-scheduler
cd notion-x-scheduler
# Install dependencies
pip install -r requirements.txt
# Set environment variables
export NOTION_TOKEN="your_token"
export NOTION_DB_ID="your_db_id"
export OPENAI_API_KEY="your_key"
# Run dry-run test
python fetch_ai_news.py --dry-runExpected output:
{
"short_summary": "OpenAI launches new GPT-4 features... (openai.com)",
"long_form_content": "MAJOR AI BREAKTHROUGH\n\nOpenAI has unveiled...",
"long_form_length": 2847,
"title": "OpenAI launches new GPT-4 features...",
"link": "https://openai.com/blog/...",
"note": "dry-run: Notion write skipped"
}- Go to Actions tab in your repository
- Select "AI Content Fetcher" workflow
- Click "Run workflow"
- Set
dry_runtofalse - Click "Run workflow" button
- Watch the workflow execute in real-time! β¨
After running the workflow:
-
β Check Notion: You should see a new row with:
Status= "Scheduled"Tweet Content= Short titleLong Form Draft= Full long-form content
-
β Check X/Twitter: 5 minutes later, the post should appear on your timeline
-
β Check GitHub Actions logs:
- Green checkmarks = success! π
- Red X = check error logs for troubleshooting
Your system is now fully automated! It will:
- β Run every day at 9:00am Sydney time
- β Find the best AI news from 11 premium sources
- β Generate a premium long-form post (2-5K characters)
- β Save it to Notion
- β Post it to X/Twitter at 9:05am
- β Track everything with proper status updates
No more manual work needed! π
The system uses dual UTC cron schedules to maintain consistent Sydney local time:
schedule:
- cron: "0 22 * 10-12,1-3 *" # 22:00 UTC = 9:00am AEDT (Sydney summer)
- cron: "0 23 * 4-9 *" # 23:00 UTC = 9:00am AEST (Sydney winter)schedule:
- cron: "5 22 * 10-12,1-3 *" # 22:05 UTC = 9:05am AEDT (Sydney summer)
- cron: "5 23 * 4-9 *" # 23:05 UTC = 9:05am AEST (Sydney winter)Why 5-minute gap? Ensures Notion writes complete before posting begins.
Each article gets a score based on:
# Recency scoring (out of 15 points)
0-6 hours: +15 points # Super fresh
6-12 hours: +12 points # Very fresh
12-24 hours: +8 points # Fresh
24-36 hours: +3 points # Recent
36-48 hours: +1 point # Older
>48 hours: Filtered out
# Keyword matching (+2 per keyword)
Keywords: AI, GenAI, LLM, agents, model, inference,
NVIDIA, OpenAI, Anthropic, Meta
# Source diversity penalty
>50% from one source: -10 Γ (frequency - 0.5)
>30% from one source: -5 Γ (frequency - 0.3)
# Duplicate penalty (similarity with recent posts)
>60% similar: -15 Γ (similarity - 0.6)Result: The highest-scoring article wins! π
CRITICAL FORMATTING RULES FOR X/TWITTER:
- DO NOT use markdown (no ##, no **, no *, no _)
- X/Twitter does NOT render markdown - it shows raw symbols
- Use PLAIN TEXT formatting only
- Use line breaks and spacing for structure
- Use ALL CAPS for emphasis (sparingly)
Target: 2,000-5,000 characters
Maximum: 25,000 characters (X Premium limit)
MAJOR AI BREAKTHROUGH
OpenAI has just unveiled groundbreaking updates to GPT-4 that
fundamentally change how we interact with AI systems.
WHAT'S NEW
β’ Enhanced reasoning capabilities with 40% improvement
β’ Native support for multimodal inputs
β’ Reduced latency by 60%
WHY THIS MATTERS
These advances represent a significant leap forward in making AI
more accessible and practical for everyday users...
[continues for 2,000-5,000 characters]
Notion rich_text blocks have a 2000-character limit. The system automatically chunks content:
chunks = []
for i in range(0, len(long_form_content), 2000):
chunks.append({
"type": "text",
"text": {"content": long_form_content[i:i+2000]}
})
properties["Long Form Draft"] = {"rich_text": chunks}Result: Seamless storage of 2-25K character posts! β
# Query last 7 days of posts
# Calculate word overlap similarity
# Penalize articles >70% similar to recent postssequenceDiagram
participant G as GitHub Actions
participant F as fetch_ai_news.py
participant O as OpenAI API
participant N as Notion
participant C as check_ready_to_post.py
participant M as main.py
participant X as X/Twitter
G->>F: Trigger at 9:00am Sydney
F->>F: Parse 11 RSS feeds
F->>F: Score & rank articles
F->>F: Select top article
F->>O: Generate long-form content
O-->>F: 2,000-5,000 char post
F->>N: Save (with chunking)
N-->>F: Success
Note over G: Wait 5 minutes
G->>C: Trigger at 9:05am Sydney
C->>N: Check for Scheduled posts
N-->>C: Posts found
C->>M: Proceed
M->>N: Fetch content
N-->>M: Long Form Draft
M->>X: POST /2/tweets
X-->>M: Success
M->>N: Update Status=Posted
notion-x-scheduler/
β
βββ π fetch_ai_news.py # Main fetcher script (775 lines)
β βββ RSS feed parsing
β βββ Scoring algorithm
β βββ OpenAI integration
β βββ Notion writer (with chunking)
β βββ Duplicate detection
β
βββ π main.py # X/Twitter poster (195 lines)
β βββ Notion reader
β βββ X API v2 integration
β βββ Status updater
β βββ Error handling
β
βββ π check_ready_to_post.py # Pre-check validator
β βββ Ensures posts exist before running poster
β
βββ π requirements.txt # Python dependencies
β βββ openai>=2.0.0 # OpenAI SDK v2
β βββ notion-client==2.2.1 # Notion API
β βββ requests-oauthlib==2.0.0 # X OAuth
β βββ [other deps...]
β
βββ π .github/workflows/
β βββ fetch.yml # Fetch workflow (9:00am Sydney)
β βββ post.yml # Post workflow (9:05am Sydney)
β
βββ π README.md # You are here! π
- Lines 1-67: Configuration and imports
- Lines 68-158: RSS feed parsing with retry logic
- Lines 159-253: Scoring algorithm with duplicate detection
- Lines 254-370: OpenAI summarization (short + long-form)
- Lines 371-460: Long-form content generation
- Lines 461-550: Notion integration with chunking
- Lines 551-775: Main execution flow
- Reads
Long Form Draftproperty from Notion - Posts to X using API v2 (25K character support)
- Updates Notion status to
PostedorFailed - Handles errors gracefully with detailed logging
- fetch.yml: Runs at 9:00am Sydney (dual UTC crons for DST)
- post.yml: Runs at 9:05am Sydney + event-chained trigger
| Service | Usage | Cost | Notes |
|---|---|---|---|
| GitHub Actions | ~60 min/month | $0.00 | 3% of free tier (2,000 min/month) |
OpenAI gpt-4o-mini |
~30 calls | $0.01 | $0.150/1M input + $0.600/1M output tokens |
| Notion API | Unlimited | $0.00 | Included in free/paid plans |
| X API v2 | ~30 posts | $0.00 | Free tier (Basic/Free/Pro plans) |
| TOTAL | <$0.01 | Virtually free! π |
- Already using GPT-4? Change
OPENAI_MODELsecret for better quality (~$0.30/month) - Want to save more? Remove
OPENAI_API_KEYfor heuristic fallback (100% free!) - Scale up? Add more feeds or increase frequency (still very cheap!)
# Run dry-run locally
python fetch_ai_news.py --dry-run
# Expected: JSON output with long-form content# Unset OpenAI key
unset OPENAI_API_KEY
# Run again
python fetch_ai_news.py --dry-run
# Expected: Heuristic summary (shorter, but works!)# If no articles in 48h window
# Expected: "No fresh items (β€48h); Skipped."
# Notion: Creates row with Status=Skipped- Go to Actions tab
- Click AI Content Fetcher
- Click Run workflow
- Set
dry_run = false - Monitor logs for success β¨
Error: body.properties.Long Form Draft.rich_text[0].text.content.length
should be β€ 2000, instead was 5434
Solution: This is fixed in v2.0! The chunking logic automatically splits content into 2000-char blocks.
Solutions:
- Verify your X app has Read & Write permissions
- Regenerate access tokens after changing permissions
- Ensure you're using OAuth 1.0a credentials (not OAuth 2.0)
- Check your X account has Premium/Pro (for 25K char posts)
Solutions:
- Check your OpenAI billing page (ensure you have credits)
- Verify your API key is valid
- If hitting rate limits, reduce model to
gpt-3.5-turbo
Solutions:
- Check the workflow is enabled (Actions β workflow name β Enable)
- Verify cron schedule matches your timezone expectation
- GitHub Actions can have up to 10-minute delays for scheduled workflows
- Use manual Run workflow to test immediately
Behavior: This is normal! The system checks last 7 days and applies similarity penalties.
If you want stricter duplicate detection:
# In fetch_ai_news.py, line ~215
if any(title_similarity(norm_title, n[0]) > 0.7 for n in notion_seen):
# Change 0.7 to 0.5 for stricter matching- Go to Actions tab
- Click on a workflow run
- Expand each step to see detailed logs
Look for these key indicators:
- β
β Successfully saved long-form content (XXXX chars) to Notion - β
β SUCCESS: Generated long-form content: 220 β 2847 chars - β
Posted tweet ID: 1234567890
Your database should show:
- Status = Scheduled: Waiting to be posted
- Status = Posted: Successfully published to X
- Status = Failed: Error occurred (check Error Message property)
- Status = Skipped: No fresh news found that day
- Posts should appear at 9:05am Sydney time
- Content should be 2,000-5,000 characters
- Formatting should be clean (no
###or**symbols)
Edit .github/workflows/fetch.yml:
schedule:
- cron: "0 22 * 10-12,1-3 *" # Change time hereUse https://crontab.guru/ to help with cron syntax (remember: GitHub uses UTC!)
Edit fetch_ai_news.py around line 39:
RSS_FEEDS = [
"https://openai.com/blog/rss.xml",
"https://your-new-feed.com/rss", # Add here!
# ...
]Edit fetch_ai_news.py around line 57:
BOOST_KEYWORDS = [
"AI", "GenAI", "LLM",
"your-keyword", # Add your keywords!
]Edit fetch_ai_news.py around line 490:
Target: 2,000-5,000 characters for most posts
# Change to: Target: 1,000-3,000 characters (shorter)
# Or: Target: 5,000-10,000 characters (longer)Add GitHub Secret OPENAI_MODEL with one of:
gpt-4o-mini(default, cheapest)gpt-4o(better quality, more expensive)gpt-4(best quality, most expensive)gpt-3.5-turbo(faster, cheaper, lower quality)
We welcome contributions! Here's how:
- Fork this repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- π Support for more RSS feeds
- π¨ Better content formatting
- π Analytics/metrics tracking
- π Slack/Discord notifications
- π§ͺ Unit tests
- π Translation to other languages
If this project helped you, please give it a β on GitHub!
MIT License - feel free to use this project for personal or commercial purposes!
- OpenAI for GPT-4o-mini API
- Notion for amazing database API
- X/Twitter for API v2 with long-form support
- GitHub Actions for free CI/CD
- All contributors who improve this project!
- π Found a bug? Open an issue
- π‘ Have an idea? Start a discussion
- π§ Need help? Check the troubleshooting section above first!
Built with β€οΈ by @skalaliya
Automating AI content, one post at a time! π€β¨