GitHub Actions for Social Media: Auto-Post When You Ship Code

TL;DR
Automatically announce releases on social media with GitHub Actions and Publora API. Complete workflow YAML, AI-generated changelogs, multi-platform customization, and GitLab CI adaptation.
Why Developers Should Announce Releases on Social Media
You just shipped a feature. The pull request is merged, CI/CD is green, the release tag is pushed. But outside your team's Slack channel, nobody knows. Your users don't check your changelog page. Potential users aren't monitoring your GitHub releases. The feature you spent weeks building goes unnoticed by the people who would benefit most from it.
This is the developer marketing gap. Shipping code is the hard part, but announcing it takes 30 seconds — if it's automated. By connecting your CI/CD pipeline to social media via GitHub Actions + Publora, every release, tag, or merged PR can automatically generate announcements across LinkedIn, X (Twitter), Telegram, and more. No manual posting. No forgetting. No context-switching from your IDE to a social media dashboard.
What you'll learn in this guide:
- Why CI/CD social media automation matters for developer-led products
- A complete
.github/workflows/social-announce.ymlyou can copy into your repo- How to use Publora's API inside GitHub Actions (curl and Python)
- Customizing announcement text per platform (X, LinkedIn, Telegram)
- Adding AI-generated changelog summaries for AI workflow automation
The Case for Automated Release Announcements
Let's be direct: most open-source projects and developer tools are terrible at marketing their releases. The code is excellent, the documentation is thorough, but the announcement is a one-line tweet — if it happens at all.
72%
Of GitHub releases have zero social media announcements
3x
More adoption when releases are actively shared on social
30s
Time to set up automated announcements (once)
Automating this solves three problems at once:
- Consistency — every release gets announced, not just the ones you remember
- Speed — announcements go out within minutes of the release, not hours or days later
- Multi-platform reach — one automation posts to LinkedIn (for enterprise users), X (for the dev community), Telegram (for your community channel), and more
Architecture: GitHub Actions + Publora API
The setup is straightforward. GitHub Actions provides the trigger and execution environment; Publora provides the multi-platform social media API.
GitHub Event
Release published or tag pushed
Format Text
Extract release notes, build announcement
Publora API
Create post via REST API
Published
Live on LinkedIn, X, Telegram, etc.
Prerequisites
Publora Account
Sign up at app.publora.com. Connect your social media accounts (LinkedIn, X, Telegram, etc.) via the Connections page. Generate an API key under Settings.
GitHub Repository Secrets
Store your Publora API key as PUBLORA_API_KEY and your platform IDs as PUBLORA_PLATFORM_IDS in your repo's Settings → Secrets and variables → Actions.
The Complete Workflow: social-announce.yml
Here is a production-ready GitHub Actions workflow that you can copy directly into your repository. Save it as .github/workflows/social-announce.yml.
Version 1: Simple (curl)
This version uses plain curl to call the Publora API. It's the simplest approach — no dependencies, no setup, works immediately.
# .github/workflows/social-announce.yml
name: Announce Release on Social Media
on:
release:
types: [published]
permissions:
contents: read
jobs:
announce:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Build announcement text
id: build-text
run: |
RELEASE_NAME="${{ github.event.release.name }}"
RELEASE_TAG="${{ github.event.release.tag_name }}"
RELEASE_URL="${{ github.event.release.html_url }}"
REPO_NAME="${{ github.repository }}"
# Extract first 3 lines of release body for summary
RELEASE_BODY=$(echo '${{ github.event.release.body }}' | head -20 | sed 's/"/\\"/g')
# Build the announcement
ANNOUNCEMENT=$(cat <<'ENDOFTEXT'
We just released ${RELEASE_NAME} (${RELEASE_TAG})!
Key changes:
${RELEASE_BODY}
Check it out: ${RELEASE_URL}
#opensource #release #devtools
ENDOFTEXT
)
# Use envsubst for variable expansion
ANNOUNCEMENT=$(echo "$ANNOUNCEMENT" | \
RELEASE_NAME="$RELEASE_NAME" \
RELEASE_TAG="$RELEASE_TAG" \
RELEASE_URL="$RELEASE_URL" \
RELEASE_BODY="$RELEASE_BODY" \
envsubst)
echo "announcement<> $GITHUB_OUTPUT
echo "$ANNOUNCEMENT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Post to social media via Publora
run: |
curl -s -f -X POST \
https://api.publora.com/api/v1/create-post \
-H "Content-Type: application/json" \
-H "x-publora-key: ${{ secrets.PUBLORA_API_KEY }}" \
-d '{
"content": ${{ toJSON(steps.build-text.outputs.announcement) }},
"platforms": ${{ secrets.PUBLORA_PLATFORM_IDS }}
}'
continue-on-error: true # Don't fail the release if posting fails
Important: continue-on-error
The continue-on-error: true on the posting step is intentional. You don't want a social media API failure to mark your release workflow as failed. The release itself succeeded — the announcement is a nice-to-have. If you want to be notified of failures, add a Slack/email notification step after it.
Version 2: Advanced (Python with platform-specific text)
This version uses a Python script that generates different announcement text for each platform — short and punchy for X, professional and detailed for LinkedIn, Markdown-formatted for Telegram.
# .github/workflows/social-announce.yml
name: Announce Release on Social Media
on:
release:
types: [published]
workflow_dispatch:
inputs:
dry_run:
description: 'Post as draft (true/false)'
required: false
default: 'false'
permissions:
contents: read
jobs:
announce:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: pip install requests
- name: Post announcements
env:
PUBLORA_API_KEY: ${{ secrets.PUBLORA_API_KEY }}
LINKEDIN_PLATFORM_ID: ${{ vars.LINKEDIN_PLATFORM_ID }}
X_PLATFORM_ID: ${{ vars.X_PLATFORM_ID }}
TELEGRAM_PLATFORM_ID: ${{ vars.TELEGRAM_PLATFORM_ID }}
RELEASE_NAME: ${{ github.event.release.name || 'Manual trigger' }}
RELEASE_TAG: ${{ github.event.release.tag_name || 'latest' }}
RELEASE_URL: ${{ github.event.release.html_url || github.server_url }}
RELEASE_BODY: ${{ github.event.release.body || 'No release notes.' }}
REPO_NAME: ${{ github.repository }}
DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
run: python .github/scripts/announce_release.py
continue-on-error: true
And the Python script at .github/scripts/announce_release.py:
#!/usr/bin/env python3
"""Post release announcements to social media via Publora API."""
import json
import os
import requests
import sys
API_KEY = os.environ["PUBLORA_API_KEY"]
BASE_URL = "https://api.publora.com/api/v1"
HEADERS = {
"Content-Type": "application/json",
"x-publora-key": API_KEY,
}
# Release metadata from GitHub Actions environment
release_name = os.environ.get("RELEASE_NAME", "New Release")
release_tag = os.environ.get("RELEASE_TAG", "latest")
release_url = os.environ.get("RELEASE_URL", "")
release_body = os.environ.get("RELEASE_BODY", "")
repo_name = os.environ.get("REPO_NAME", "")
dry_run = os.environ.get("DRY_RUN", "false") == "true"
# Extract first few bullet points from release notes
def summarize_changelog(body: str, max_items: int = 5) -> str:
"""Extract key changes from release notes markdown."""
lines = body.strip().split("\n")
changes = []
for line in lines:
line = line.strip()
if line.startswith(("- ", "* ", "- [ ]", "- [x]")):
# Clean up markdown checkboxes
clean = line.lstrip("-*[] x").strip()
if clean:
changes.append(clean)
if len(changes) >= max_items:
break
return "\n".join(f"- {c}" for c in changes) if changes else "See release notes for details."
changelog_summary = summarize_changelog(release_body)
# --- Platform-specific content ---
linkedin_text = f"""{release_name} ({release_tag}) is now available!
Here's what's new:
{changelog_summary}
We've been working hard on this release and we're excited to share it with you.
Check out the full release notes: {release_url}
#opensource #release #software #devtools #{repo_name.split('/')[-1].replace('-', '')}"""
x_text = f"""{release_name} ({release_tag}) is out!
{changelog_summary[:180]}
{release_url}
#opensource #{repo_name.split('/')[-1].replace('-', '')}"""
telegram_text = f"""**{release_name}** (`{release_tag}`) released!
**What's new:**
{changelog_summary}
[Full release notes]({release_url})"""
# --- Post to each platform ---
platforms = {
"linkedin": {
"id": os.environ.get("LINKEDIN_PLATFORM_ID"),
"text": linkedin_text,
},
"x": {
"id": os.environ.get("X_PLATFORM_ID"),
"text": x_text,
},
"telegram": {
"id": os.environ.get("TELEGRAM_PLATFORM_ID"),
"text": telegram_text,
},
}
results = []
for platform_name, config in platforms.items():
if not config["id"]:
print(f"Skipping {platform_name}: no platform ID configured")
continue
payload = {
"content": config["text"],
"platforms": [config["id"]],
}
if dry_run:
payload["status"] = "draft"
print(f"[DRY RUN] Creating draft for {platform_name}")
try:
resp = requests.post(
f"{BASE_URL}/create-post",
headers=HEADERS,
json=payload,
timeout=30,
)
resp.raise_for_status()
data = resp.json()
post_id = data.get("postGroupId", "unknown")
print(f"Posted to {platform_name}: postGroupId={post_id}")
results.append({"platform": platform_name, "success": True, "id": post_id})
except requests.exceptions.RequestException as e:
print(f"Failed to post to {platform_name}: {e}", file=sys.stderr)
results.append({"platform": platform_name, "success": False, "error": str(e)})
# Summary
print(f"\n--- Results ---")
succeeded = sum(1 for r in results if r["success"])
print(f"{succeeded}/{len(results)} platforms posted successfully")
if not all(r["success"] for r in results):
print("Some posts failed. Check the logs above for details.")
# Don't sys.exit(1) — we don't want to fail the GitHub Action
Storing Secrets Securely
Never hardcode API keys in your workflow files. GitHub provides encrypted secrets specifically for this purpose.
Setting Up Repository Secrets
- Go to your repository on GitHub
- Click Settings → Secrets and variables → Actions
- Click New repository secret
- Add these secrets:
| Secret Name | Value | Purpose |
|---|---|---|
PUBLORA_API_KEY |
sk_your_api_key |
Authenticates API calls |
PUBLORA_PLATFORM_IDS |
["linkedin-abc","x-def"] |
JSON array of platform IDs (for simple curl version) |
For the advanced Python version, use repository variables (not secrets) for platform IDs since they're not sensitive:
| Variable Name | Value |
|---|---|
LINKEDIN_PLATFORM_ID |
linkedin-your-id |
X_PLATFORM_ID |
x-your-id |
TELEGRAM_PLATFORM_ID |
telegram-your-id |
Security reminder
- Never commit
.envfiles or API keys to your repository - GitHub secrets are encrypted at rest and masked in logs
- Use fine-grained API keys — Publora lets you create keys with specific scopes
- Rotate keys periodically and revoke unused ones from the Publora dashboard
Customizing Announcement Text Per Platform
Each social platform has different norms, character limits, and audience expectations. The advanced Python script handles this, but here's the thinking behind each platform's formatting.
- Tone: Professional, detailed
- Length: Up to 3,000 chars (aim for 800-1,300)
- Format: Full paragraphs, bullet points for changes
- Hashtags: 3-5 industry hashtags
- CTA: "Check out the release notes" with link
X (Twitter)
- Tone: Punchy, exciting
- Length: 280 chars max per tweet
- Format: Hook + 1-2 highlights + link
- Hashtags: 1-2 relevant hashtags
- Tip: Use threads for longer changelogs
Telegram
- Tone: Direct, informative
- Length: Up to 4,096 chars
- Format: Markdown bold, inline links
- Hashtags: Optional
- Tip: Include full changelog — Telegram users expect detail
- Tone: Visual-first, casual
- Length: 2,200 chars
- Format: Requires image — generate a release card
- Hashtags: Up to 30
- Tip: Use a branded template image with version number
Adding AI-Generated Changelog Summaries
Release notes can be long and technical. An AI model can summarize them into a compelling social media post. This is where AI workflow automation meets CI/CD. Here's how to add an LLM step to your workflow.
- name: Generate AI summary
id: ai-summary
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
RELEASE_BODY: ${{ github.event.release.body }}
RELEASE_NAME: ${{ github.event.release.name }}
run: |
SUMMARY=$(python3 -c "
import json, os, requests
resp = requests.post('https://api.openai.com/v1/chat/completions',
headers={
'Authorization': f'Bearer {os.environ[\"OPENAI_API_KEY\"]}',
'Content-Type': 'application/json'
},
json={
'model': 'gpt-4o-mini',
'messages': [{
'role': 'system',
'content': 'You are a developer advocate. Summarize this changelog into a compelling social media announcement. Max 250 characters. Focus on user benefits, not technical details. Be enthusiastic but authentic.'
}, {
'role': 'user',
'content': f'Release: {os.environ[\"RELEASE_NAME\"]}\n\nChangelog:\n{os.environ[\"RELEASE_BODY\"]}'
}],
'max_tokens': 100
},
timeout=30
)
print(resp.json()['choices'][0]['message']['content'])
")
echo "summary<> $GITHUB_OUTPUT
echo "$SUMMARY" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
Then reference ${{ steps.ai-summary.outputs.summary }} in your Publora API call. The AI turns technical changelogs like "refactored database connection pooling for 40% faster query execution" into social-friendly text like "Your queries just got 40% faster. Here's what we shipped this week."
Cost note
GPT-4o-mini costs about $0.15 per 1M input tokens. A typical changelog summary request costs less than $0.001 — essentially free. If you prefer, use a free model via Ollama in your CI or skip the AI step entirely and use the template-based approach from Version 1.
Alternative Triggers: Beyond Releases
The release event is the most common trigger, but GitHub Actions supports many events that can drive social media posts.
| Trigger | YAML | Use Case |
|---|---|---|
| Release published | on: release: types: [published] |
Standard release announcement |
| Tag pushed | on: push: tags: ['v*'] |
Announce when version tags are pushed |
| Manual trigger | on: workflow_dispatch |
Post on demand with custom inputs |
| Cron schedule | on: schedule: cron: '0 14 * * 1' |
Weekly "What we shipped" digest |
| PR merged | on: pull_request: types: [closed] |
Announce major features when PR merges |
| Issue milestone | on: milestone: types: [closed] |
Celebrate milestone completion |
Example: Weekly Digest (Cron-Triggered)
on:
schedule:
- cron: '0 14 * * 1' # Every Monday at 2 PM UTC
jobs:
weekly-digest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for git log
- name: Build weekly digest
id: digest
run: |
# Get commits from the last 7 days
SINCE=$(date -d '7 days ago' --iso-8601)
COMMITS=$(git log --since="$SINCE" --oneline --no-merges | head -10)
DIGEST="What we shipped this week:
$COMMITS
Follow along as we build in public!
#buildinpublic #devlog #opensource"
echo "digest<> $GITHUB_OUTPUT
echo "$DIGEST" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Post digest via Publora
run: |
curl -s -f -X POST \
https://api.publora.com/api/v1/create-post \
-H "Content-Type: application/json" \
-H "x-publora-key: ${{ secrets.PUBLORA_API_KEY }}" \
-d '{
"content": ${{ toJSON(steps.digest.outputs.digest) }},
"platforms": ${{ secrets.PUBLORA_PLATFORM_IDS }}
}'
continue-on-error: true
Error Handling and Monitoring
Production workflows need to handle failures gracefully. Here are patterns for robust social media automation in CI/CD.
continue-on-error: true
The most important setting. A social media failure should never block your release pipeline. The code shipped successfully — that's what matters.
Retry with backoff
For transient errors (network timeouts, rate limits), retry 2-3 times with increasing delays. The Python script can wrap API calls in a retry loop.
Slack/email notification
Add a final step that sends a Slack message if posting failed. You'll know to post manually while debugging the automation.
Dry run support
Add workflow_dispatch with a dry_run input that creates drafts instead of publishing. Test your workflow without spamming your followers.
# Add after the posting step
- name: Notify on failure
if: steps.post-social.outcome == 'failure'
run: |
curl -X POST ${{ secrets.SLACK_WEBHOOK_URL }} \
-H "Content-Type: application/json" \
-d '{
"text": "Social media announcement failed for ${{ github.event.release.tag_name }}. Check the workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}'
Adapting for GitLab CI/CD
If your repository lives on GitLab instead of GitHub, the Publora API calls are identical — only the CI/CD syntax changes.
# .gitlab-ci.yml
announce-release:
stage: deploy
image: python:3.12-slim
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+/ # Triggers on version tags
variables:
PUBLORA_API_KEY: $PUBLORA_API_KEY # Set in GitLab CI/CD Variables
script:
- pip install requests
- python .gitlab/scripts/announce_release.py
allow_failure: true # GitLab equivalent of continue-on-error
The Python script is the same as the GitHub version — just adjust environment variable names (CI_COMMIT_TAG instead of github.event.release.tag_name, etc.).
Real-World Examples: What Good Release Announcements Look Like
Here are examples of effective automated release announcements, showing the input (GitHub release) and output (social media post).
GitHub Release (input):
Title: v2.4.0 - Batch Scheduling & Telegram Markdown
Body:
- Added batch scheduling: create up to 50 posts in a single API call
- Telegram posts now support full Markdown formatting
- Fixed: timezone handling for DST transitions
- Performance: 60% faster media upload processing
LinkedIn (output):
We just released v2.4.0 - Batch Scheduling & Telegram Markdown!
Key changes:
- Batch scheduling: create up to 50 posts in a single API call
- Telegram posts now support full Markdown formatting
- Fixed timezone handling for DST transitions
- 60% faster media upload processing
Check out the full release notes: https://github.com/org/repo/releases/tag/v2.4.0
#opensource #release #devtools
X/Twitter (output):
v2.4.0 is out! Batch scheduling (50 posts/call), Telegram Markdown, and 60% faster uploads.
https://github.com/org/repo/releases/tag/v2.4.0
#opensource
Telegram (output):
v2.4.0 - Batch Scheduling & Telegram Markdown released!
What's new:
- Batch scheduling: create up to 50 posts in a single API call
- Telegram posts now support full Markdown formatting
- Fixed timezone handling for DST transitions
- 60% faster media upload processing
Full release notes
Getting Started in 5 Minutes
Here's the fastest path to automated release announcements:
- Create a Publora account and connect your social media accounts
- Generate an API key — Settings → API Keys → Create Key (docs)
- Add secrets to your GitHub repo —
PUBLORA_API_KEYandPUBLORA_PLATFORM_IDS - Copy the workflow YAML into
.github/workflows/social-announce.yml - Create a release on GitHub — the workflow triggers automatically
Ship code. Announce automatically.
Connect GitHub Actions to Publora and never forget to announce a release again.
Get Started Free →Frequently Asked Questions
Can GitHub Actions post to social media automatically?
Yes. GitHub Actions can run any code when events like releases, tags, or pushes occur. By adding a workflow step that calls a social media API (like Publora), you can automatically announce releases on LinkedIn, X, Telegram, Instagram, and other platforms. The workflow YAML file lives in your repository under .github/workflows/.
Is it free to use GitHub Actions for social media posting?
GitHub Actions is free for public repositories and includes 2,000 minutes per month for private repositories on the free plan. A social media announcement workflow takes about 10-30 seconds per run, so you could run thousands of announcements per month within the free tier. Publora's API is available on the Pro plan and above.
What GitHub events can trigger social media posts?
Common triggers include: release (when you publish a GitHub Release), push with tag filter (when you push a version tag like v1.2.0), workflow_dispatch (manual trigger with inputs), schedule (cron-based posting), and pull_request with type closed. The release event is the most popular for announcement workflows.
How do I keep my Publora API key secure in GitHub Actions?
Store your API key as a GitHub Actions secret: go to your repository Settings → Secrets and variables → Actions → New repository secret. Name it PUBLORA_API_KEY. In your workflow YAML, reference it as ${{ secrets.PUBLORA_API_KEY }}. GitHub masks secrets in logs and never exposes them in workflow outputs. See the Publora authentication docs for API key management.
Can I customize the announcement text per social media platform?
Yes. You can either send the same text to all platforms in one Publora API call (using the platforms array), or make separate API calls with platform-specific text. The advanced Python workflow above shows how to generate short text for X (280 chars), detailed text for LinkedIn (1,300 chars), and Markdown-formatted text for Telegram.
How do I include the changelog or release notes in the social media post?
GitHub's release event payload includes the release body (your release notes) at github.event.release.body. You can extract key points from it using a script step (Python, Node.js, or bash) and include them in your announcement. For AI-powered summaries, pipe the changelog through an LLM API (like GPT-4o-mini) to generate a concise, social-friendly post.
Can I use this with GitLab CI/CD instead of GitHub Actions?
Yes. The Publora API calls are identical — only the CI/CD syntax differs. In GitLab CI, use a .gitlab-ci.yml file with a stage that runs curl or Python to call the Publora API. Store your API key as a GitLab CI/CD variable (Settings → CI/CD → Variables). The workflow logic is the same. See the GitLab CI/CD section above for a ready-to-use example.
What happens if the social media post fails during a GitHub Actions run?
By default, a failed API call will fail the workflow step. We recommend adding continue-on-error: true to the posting step so the release succeeds even if the announcement fails. You can also check the HTTP status code and retry, or add a Slack/email notification step that alerts you when posting fails. The release itself should never be blocked by a social media error.
Further Reading
- Publora API Reference — Complete endpoint documentation
- Authentication Guide — API key creation and management
- Create Post Endpoint — Detailed request/response reference
- Rate Limits — Platform and API rate limit details
- GitHub Actions Events Reference — All available trigger events
- n8n + Publora Guide — No-code alternative to CI/CD automation
Related Articles

Publora + Make Integromat: Automate Cross-Platform Social Campaigns
Set up Publora as an HTTP module in Make for automated social campaigns. Three scenarios: multi-platform with Router, content approval with Slack, and analytics-triggered reposting.

Automate Social Media with n8n + Publora: No-Code Workflow Guide
Connect n8n to Publora for no-code social media automation. Three importable workflows: RSS auto-post, batch scheduling from Google Sheets, and webhook-triggered posts.

Why Zapier Isnt Enough for Social Media Automation and What to Use Instead
Six specific limitations of Zapier for social media automation: per-task pricing, limited platforms, no analytics, no media upload, no MCP. Cost analysis and Publora API alternative.

Publora vs Buffer: The API-First Alternative for Technical Teams
Detailed comparison of Publora and Buffer APIs, pricing, platform support, and developer features. Side-by-side code examples, media upload differences, and migration guide.