| name | social-media |
| description | Add or update social media posting integrations (Discord, LinkedIn, Telegram, Twitter) in workflows. Use when adding new platforms, debugging posting failures, or modifying message templates. |
| allowed-tools | Read, Edit, Grep, Glob |
Social Media Integration Skill
This skill helps you work with social media integrations in apps/api/src/lib/workflows/social/.
When to Use This Skill
- Adding new social media platforms
- Debugging posting failures
- Updating message templates and formatting
- Configuring webhook URLs and API credentials
- Testing social media workflows
Supported Platforms
Current integrations:
- Discord - Webhook-based posting
- LinkedIn - OAuth-based API posting
- Telegram - Bot API posting
- Twitter - API v2 posting
Architecture
apps/api/src/lib/workflows/social/
├── discord.ts # Discord webhook integration
├── linkedin.ts # LinkedIn API integration
├── telegram.ts # Telegram bot integration
└── twitter.ts # Twitter API integration
Key Patterns
1. Discord Integration
Discord uses webhooks for simple posting:
export async function postToDiscord(message: string, data: any) {
const webhookUrl = process.env.DISCORD_WEBHOOK_URL;
if (!webhookUrl) {
throw new Error("Discord webhook URL not configured");
}
const embed = {
title: "New Data Available",
description: message,
color: 0x00ff00,
fields: [
{ name: "Date", value: data.date, inline: true },
{ name: "Count", value: String(data.count), inline: true },
],
timestamp: new Date().toISOString(),
};
await fetch(webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ embeds: [embed] }),
});
}
2. Telegram Integration
Telegram uses Bot API with chat IDs:
export async function postToTelegram(message: string) {
const botToken = process.env.TELEGRAM_BOT_TOKEN;
const chatId = process.env.TELEGRAM_CHAT_ID;
if (!botToken || !chatId) {
throw new Error("Telegram credentials not configured");
}
const url = `https://api.telegram.org/bot${botToken}/sendMessage`;
await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: chatId,
text: message,
parse_mode: "Markdown",
disable_web_page_preview: false,
}),
});
}
3. Twitter Integration
Twitter uses OAuth 2.0 with API v2:
export async function postToTwitter(message: string) {
const bearerToken = process.env.TWITTER_BEARER_TOKEN;
if (!bearerToken) {
throw new Error("Twitter bearer token not configured");
}
const url = "https://api.twitter.com/2/tweets";
const response = await fetch(url, {
method: "POST",
headers: {
"Authorization": `Bearer ${bearerToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ text: message }),
});
if (!response.ok) {
throw new Error(`Twitter API error: ${response.statusText}`);
}
return await response.json();
}
4. LinkedIn Integration
LinkedIn uses OAuth 2.0 with organization posting:
export async function postToLinkedIn(message: string) {
const accessToken = process.env.LINKEDIN_ACCESS_TOKEN;
const organizationId = process.env.LINKEDIN_ORG_ID;
if (!accessToken || !organizationId) {
throw new Error("LinkedIn credentials not configured");
}
const url = "https://api.linkedin.com/v2/ugcPosts";
await fetch(url, {
method: "POST",
headers: {
"Authorization": `Bearer ${accessToken}`,
"Content-Type": "application/json",
"X-Restli-Protocol-Version": "2.0.0",
},
body: JSON.stringify({
author: `urn:li:organization:${organizationId}`,
lifecycleState: "PUBLISHED",
specificContent: {
"com.linkedin.ugc.ShareContent": {
shareCommentary: { text: message },
shareMediaCategory: "NONE",
},
},
visibility: {
"com.linkedin.ugc.MemberNetworkVisibility": "PUBLIC",
},
}),
});
}
Message Templates
Create reusable message templates:
export function createCarDataMessage(data: CarRegistrationData) {
const { month, year, total, topMakes } = data;
return `🚗 Car Registration Update - ${month} ${year}
📊 Total Registrations: ${total.toLocaleString()}
🏆 Top Makes: ${topMakes.slice(0, 3).join(", ")}
📈 View detailed analysis: https://sgcarstrends.com/data/${year}/${month}
#SingaporeCars #CarRegistration #DataAnalytics`;
}
export function createCOEMessage(data: COEBiddingData) {
const { biddingNo, category, premium } = data;
return `💰 COE Bidding Results - Round ${biddingNo}
Category ${category}: $${premium.toLocaleString()}
Change: ${data.change > 0 ? "+" : ""}${data.change}%
Full results: https://sgcarstrends.com/coe/${biddingNo}
#COE #Singapore #CarPrices`;
}
Common Tasks
Adding a New Platform
- Create integration file (e.g.,
instagram.ts) - Implement posting function with authentication
- Add environment variables for credentials
- Create message template
- Add to workflow that triggers posting
- Test with development credentials
Debugging Posting Failures
Check these common issues:
Authentication errors:
- Verify environment variables are set
- Check token expiration (especially OAuth)
- Validate API credentials in platform console
API rate limits:
- Check platform rate limit documentation
- Implement retry logic with backoff
- Add rate limit tracking
Message formatting:
- Verify character limits (Twitter: 280, LinkedIn: 3000)
- Check for invalid characters or formatting
- Test markdown/HTML support
Network issues:
- Add timeout handling
- Implement retry logic
- Log full error responses
Updating Message Templates
- Locate template function
- Update message structure
- Test with sample data
- Verify formatting on each platform:
- Discord: Embeds and markdown
- Telegram: Markdown or HTML
- Twitter: Plain text, URLs, hashtags
- LinkedIn: Rich text, mentions
Rate Limiting
Implement rate limiting to avoid API restrictions:
import { Ratelimit } from "@upstash/ratelimit";
import { redis } from "@/config/redis";
const ratelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, "1 h"), // 10 posts per hour
});
export async function postToTwitter(message: string) {
const { success } = await ratelimit.limit("twitter-posts");
if (!success) {
throw new Error("Rate limit exceeded for Twitter");
}
// Post to Twitter...
}
Environment Variables
Discord
DISCORD_WEBHOOK_URL- Webhook URL from Discord channel settings
Telegram
TELEGRAM_BOT_TOKEN- Bot token from @BotFatherTELEGRAM_CHAT_ID- Channel or group chat ID
TWITTER_BEARER_TOKEN- OAuth 2.0 bearer tokenTWITTER_API_KEY- API key (if using OAuth 1.0a)TWITTER_API_SECRET- API secret
LINKEDIN_ACCESS_TOKEN- OAuth 2.0 access tokenLINKEDIN_ORG_ID- Organization ID for company pages
Testing Social Media Posts
Run integration tests:
pnpm -F @sgcarstrends/api test -- src/lib/workflows/social
Test individual platforms:
# Start dev server
pnpm dev
# Trigger social media workflow
curl -X POST http://localhost:3000/api/workflows/social/test \
-H "Content-Type: application/json" \
-d '{"platform": "discord", "message": "Test post"}'
Error Handling
Implement comprehensive error handling:
export async function postToAllPlatforms(message: string) {
const results = await Promise.allSettled([
postToDiscord(message).catch(err => ({ platform: "Discord", error: err })),
postToTelegram(message).catch(err => ({ platform: "Telegram", error: err })),
postToTwitter(message).catch(err => ({ platform: "Twitter", error: err })),
postToLinkedIn(message).catch(err => ({ platform: "LinkedIn", error: err })),
]);
const failures = results
.filter(r => r.status === "rejected")
.map(r => r.reason);
if (failures.length > 0) {
console.error("Social media posting failures:", failures);
}
return {
success: failures.length === 0,
failures,
};
}
Platform Character Limits
Respect platform limits:
- Twitter: 280 characters
- LinkedIn: 3,000 characters (posts), 700 (comments)
- Telegram: 4,096 characters
- Discord: 2,000 characters (message), 6,000 (embed total)
Implement truncation:
export function truncateMessage(message: string, limit: number): string {
if (message.length <= limit) return message;
return message.slice(0, limit - 3) + "...";
}
References
- Platform API docs: Use Context7 for latest documentation
- Related files:
apps/api/src/lib/workflows/social/- All integrationsapps/api/src/routes/workflows.ts- Workflow routesapps/api/CLAUDE.md- API service documentation
Best Practices
- Error Handling: Always handle API failures gracefully
- Rate Limiting: Implement rate limits to avoid bans
- Credentials: Never commit API keys or tokens
- Testing: Test on sandbox/dev accounts first
- Monitoring: Track posting success rates
- Formatting: Preview messages on each platform
- Compliance: Follow platform posting guidelines
- Retries: Implement exponential backoff for retries