AI-Powered Social Media Automation Platform
XPoster is an Azure Function that automates content publishing across multiple social media platforms (Twitter/X, LinkedIn, Instagram) using artificial intelligence for content generation and curation.
- Features
- Architecture
- Technologies
- Getting Started
- Configuration
- Deployment
- Usage
- Scheduling
- Extensibility
- Testing
- Monitoring
- Roadmap
- Contributing
- License
📐 For a deep-dive into architectural decisions, design patterns, ADRs, and extension contracts, see ARCHITECTURE.md.
- AI-Powered Summarization: Intelligent RSS feed summaries using gpt-4.1-nano
- Image Generation: Automatic contextual image creation with gpt-image-1.5
- Smart Hashtags: Automatic keyword conversion to optimized hashtags
- Multi-Strategy: Support for different content generation algorithms
- Twitter/X: Automated posting with image support
- LinkedIn: Posts on personal profiles and company pages
- Instagram: Publishing via Graph API (in development)
- Timer-Based Execution: Configurable automatic execution
- Smart Scheduling: Different posting strategies based on time
- Conditional Logic: Publishing only when appropriate
- Flexible Configuration: Customizable schedule via environment variables
- Application Insights: Complete monitoring and telemetry
- Structured Logging: Detailed logs for debugging and audit
- Error Handling: Robust error management with retry logic
- Dependency Injection: Modular and testable architecture
📐 For the full architectural rationale, ADRs, design patterns, and Mermaid data-flow diagram, see ARCHITECTURE.md.
┌────────────────────────────┐
│ Azure Timer Trigger │
│ (configurable schedule) │
└───────────┬────────────────┘
│
▼
┌────────────────────────────┐
│ Generator Factory │ ◄─── Strategy Pattern
│ (Time-based Selector) │
└───────────┬────────────────┘
│
┌───────┴────────┬──────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Feed │ │ PowerLaw │ │ No │
│Generator │ │Generator │ │Generator │
└─────┬────┘ └─────┬────┘ └──────────┘
│ │
└──────┬───────┘
│
▼
┌────────────────┐
│ Services │
├────────────────┤
│ • AI Service │ ◄─── OpenAI Integration
│ • Feed Service │ ◄─── RSS Parser
│ • Crypto Svc │ ◄─── Security Utils
└────────┬───────┘
│
▼
┌────────────────┐
│ Sender Plugins │
├────────────────┤
│ • XSender │ ◄─── Twitter/X API
│ • InSender │ ◄─── LinkedIn API
│ • IgSender │ ◄─── Instagram API
└────────────────┘
Timer-triggered Azure Function that orchestrates the entire publishing workflow.
Cron Expression: Configurable via environment variable (default: 0 5 * * * *)
Dynamically selects the appropriate generator based on current time.
| Time | Platform | Strategy |
|---|---|---|
| 06:00 | Feed Summary | |
| 08:00 | Twitter/X | Feed Summary |
| 14:00 | Power Law | |
| 16:00 | Twitter/X | Power Law |
- FeedGenerator: Analyzes crypto RSS feeds, generates AI summaries, creates images
- PowerLawGenerator: Generates content based on statistical distribution
- NoGenerator: Placeholder for time slots without publishing
- AiService: Interface with OpenAI (gpt-4.1-nano, gpt-image-1.5)
- FeedService: RSS parser with caching and intelligent filtering
- CryptoService: Crypto-currencies utilities
- XSender: Twitter/X via LinqToTwitter
- InSender: LinkedIn via HTTP API
- IgSender: Instagram via Graph API (in development)
- .NET 8.0 - Main framework
- Azure Functions v4 - Serverless compute
- C# 12 - Programming language
- OpenAI - gpt-4.1-nano for summarization
- gpt-image-1.5 - Image generation
- LinqToTwitter 6.15.0 - Twitter/X integration
- LinkedIn REST API v2 - LinkedIn publishing
- Instagram Graph API - Instagram (in development)
- Application Insights - Telemetry and monitoring
- ILogger - Structured logging
- System.ServiceModel.Syndication - RSS parsing
- Microsoft.Extensions.Http - HTTP client factory
- .NET 8.0 SDK (Download)
- Azure Functions Core Tools (Install)
- Visual Studio 2022 or Visual Studio Code
- Azure Account (with active subscription)
- OpenAI API (with gpt-4.1-nano and gpt-image-1.5 enabled)
git clone https://github.com/artcava/XPoster.git
cd XPosterdotnet restoredotnet builddotnet testA template file with all required keys and inline documentation is versioned at src/local.settings.json.example.
Copy it and fill in your credentials before running the function locally:
cp src/local.settings.json.example src/local.settings.jsonThen open src/local.settings.json and replace every empty string "" with the actual value for each service. See the Configuration section for details on where to obtain each credential.
⚠️ local.settings.jsonis listed in.gitignoreand will never be committed. The.examplevariant is safe to version because it contains no real secrets.
📖 For the full expanded setup guide with troubleshooting tips, see docs/getting-started.md.
Create a local.settings.json file in the src/ directory:
{
"IsEncrypted": false,
"Values": {
"CronSchedule": "0 5 * * * *",
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"X_API_KEY": "your_twitter_api_key",
"X_API_SECRET": "your_twitter_api_secret",
"X_ACCESS_TOKEN": "your_twitter_access_token",
"X_ACCESS_TOKEN_SECRET": "your_twitter_access_token_secret",
"LINKEDIN_ACCESS_TOKEN": "your_linkedin_token",
"LINKEDIN_ORGANIZATION_ID": "your_linkedin_org_id",
"INSTAGRAM_ACCESS_TOKEN": "your_instagram_token",
"INSTAGRAM_BUSINESS_ACCOUNT_ID": "your_instagram_account_id",
"AZURE_OPENAI_ENDPOINT": "https://your-resource.openai.azure.com/",
"AZURE_OPENAI_KEY": "your_openai_key",
"AZURE_OPENAI_DEPLOYMENT_NAME": "gpt-4.1-nano"
}
}📖 Full configuration reference with types, defaults, and where to obtain each credential: docs/configuration.md.
Navigate to Azure Portal → Function App → Configuration → Application Settings
Add the same variables from local.settings.json.
For enhanced security, use Azure Managed Identity:
- Enable System Assigned Managed Identity on the Function App
- Assign appropriate roles on:
- Azure OpenAI Service
- Azure Key Vault (for secrets)
- Modify
Program.csto useDefaultAzureCredential
builder.Services.AddSingleton<OpenAIClient>(sp =>
{
var endpoint = new Uri(Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"));
return new OpenAIClient(endpoint, new DefaultAzureCredential());
});The repository includes a GitHub Actions workflow (.github/workflows/master_xposterfunction.yml).
Setup:
- Create a Function App in Azure Portal
- Download the Publish Profile from the Function App
- Add the content as a Secret in GitHub:
- Name:
AZURE_FUNCTIONAPP_PUBLISH_PROFILE
- Name:
- Every push to
mastertriggers automatic deployment
# Login
az login
# Create Resource Group
az group create --name XPosterRG --location westeurope
# Create Storage Account
az storage account create \
--name xposterstorage \
--resource-group XPosterRG \
--location westeurope \
--sku Standard_LRS
# Create Function App
az functionapp create \
--name xposterfunction \
--resource-group XPosterRG \
--consumption-plan-location westeurope \
--runtime dotnet-isolated \
--runtime-version 8 \
--functions-version 4 \
--storage-account xposterstorage
# Deploy
cd src
func azure functionapp publish xposterfunction- Right-click on the
XPosterproject - Select Publish
- Choose Azure → Azure Function App (Windows)
- Select or create a Function App
- Click Publish
📖 Step-by-step guide with post-deployment checklist: docs/deployment.md.
cd src
func startThe function will run locally according to the configured cron expression.
- Go to Azure Portal → Function App → Functions
- Select
XPosterFunction - Click Test/Run
- Click Run
Add an HTTP trigger for testing:
[Function("XPosterHttpTrigger")]
public async Task<HttpResponseData> RunHttp(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
{
await Run(null);
var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteStringAsync("XPoster executed successfully");
return response;
}The execution frequency is configurable via the CronSchedule environment variable:
Format: 6-field cron expression: {second} {minute} {hour} {day} {month} {dayOfWeek}
Configuration:
//local.settings.json
{
"Values": {
"CronSchedule": "0 5 * * * *"
}
}//Azure CLI
az functionapp config appsettings set
--name xposterfunction
--resource-group XPosterRG
--settings "CronSchedule=0 5 * * * *"| Schedule | Cron Expression | Description |
|---|---|---|
| Default | 0 5 */2 * * * |
Every 2 hours at :05 |
| Hourly | 0 0 * * * * |
Every hour on the hour |
| Every 4 hours | 0 0 */4 * * * |
Every 4 hours |
| Business Hours | 0 0 9,12,15,18 * * 1-5 |
9, 12, 15, 18 (Mon-Fri) |
| Morning/Evening | 0 0 8,20 * * * |
At 8:00 and 20:00 |
| Daily | 0 0 9 * * * |
Every day at 9:00 |
| Quick Test | */30 * * * * * |
Every 30 seconds (dev only) |
Modify GeneratorFactory.cs to customize which generator to use at each hour:
private static readonly Dictionary<int, MessageSender> sendParameters = new()
{
{ 6, MessageSender.InSummaryFeed }, // LinkedIn Feed
{ 8, MessageSender.XSummaryFeed }, // Twitter Feed
{ 10, MessageSender.IgSummaryFeed }, // Instagram Feed (enable when ready)
{ 14, MessageSender.InPowerLaw }, // LinkedIn Power Law
{ 16, MessageSender.XPowerLaw }, // Twitter Power Law
{ 18, MessageSender.IgPowerLow }, // Instagram Power Law
};✅ Testing: Use frequent schedules in development (*/5 * * * * * = every 5 secs)
✅ Production: More conservative schedules to avoid rate limiting
✅ Multi-environment: Different schedules for Dev/Staging/Prod
✅ Monitoring: Check logs to confirm correct execution
1. Create the Sender Plugin
// src/SenderPlugins/TikTokSender.cs
public class TikTokSender : ISender
{
public int MessageMaxLenght => 150;
public async Task<bool> SendAsync(Post post)
{
// Implement TikTok API logic
return true;
}
}2. Register in DI Container
// src/Program.cs
builder.Services.AddTransient<TikTokSender>();3. Add Enum
// src/Abstraction/Enums.cs
public enum MessageSender
{
// ...
TikTokSummaryFeed,
}4. Configure Factory
// src/Implementation/GeneratorFactory.cs
case MessageSender.TikTokSummaryFeed:
return GetInstance<FeedGenerator>(
_serviceProvider.GetService(typeof(TikTokSender)) as ISender
);📖 Full extension guide with services and design constraints: docs/extending-xposter.md.
// src/Implementation/QuoteGenerator.cs
public class QuoteGenerator : BaseGenerator
{
public override async Task<Post>? GenerateAsync()
{
// Logic to generate motivational quotes
var quote = await _aiService.GetQuoteAsync();
return new Post { Content = quote };
}
}tests/
├── XPoster.Tests/
│ ├── Generators/
│ │ ├── FeedGeneratorTests.cs
│ │ └── PowerLawGeneratorTests.cs
│ ├── Services/
│ │ ├── AiServiceTests.cs
│ │ └── FeedServiceTests.cs
│ └── SenderPlugins/
│ ├── XSenderTests.cs
│ └── InSenderTests.cs
# All tests
dotnet test
# Specific tests
dotnet test --filter "FullyQualifiedName~FeedGenerator"
# With coverage
dotnet test --collect:"XPlat Code Coverage"📖 Full testing strategy, mocking patterns, and coverage goals: tests/README.md.
[Fact]
public async Task FeedGenerator_ShouldGenerateSummary()
{
// Arrange
var mockAiService = new Mock<IAiService>();
mockAiService
.Setup(x => x.GetSummaryAsync(It.IsAny<string>(), It.IsAny<int>()))
.ReturnsAsync("Test summary");
var generator = new FeedGenerator(
mockSender.Object,
mockLogger.Object,
mockFeedService.Object,
mockAiService.Object
);
// Act
var result = await generator.GenerateAsync();
// Assert
Assert.NotNull(result);
Assert.Contains("Test summary", result.Content);
}- In the Azure Portal, search for Application Insights and click Create
- Fill in the details:
- Name: e.g.
xposter-appinsights - Resource Group: same as your Function App (
XPosterRG) - Region: same region as the Function App
- Resource Mode: Workspace-based (recommended)
- Name: e.g.
- Click Review + Create, then Create
- Once created, navigate to the resource and copy the Connection String (shown on the Overview blade)
Add the connection string as an Application Setting in the Function App:
Via Azure Portal:
- Go to Function App → Configuration → Application Settings
- Click + New application setting
- Name:
APPLICATIONINSIGHTS_CONNECTION_STRING - Value: paste the full connection string copied above
- Click Save and confirm the restart
Via Azure CLI:
az functionapp config appsettings set \
--name xposterfunction \
--resource-group XPosterRG \
--settings "APPLICATIONINSIGHTS_CONNECTION_STRING=InstrumentationKey=<key>;IngestionEndpoint=https://<region>.in.applicationinsights.azure.com/"The Microsoft.Azure.Functions.Worker.ApplicationInsights package is used. It is automatically registered when the connection string is present in the environment. No explicit SDK code is required in Program.cs for Azure Functions v4 isolated worker beyond the standard host builder:
// Program.cs — Application Insights is enabled automatically
// when APPLICATIONINSIGHTS_CONNECTION_STRING is set.
var host = new HostBuilder()
.ConfigureFunctionsWebApplication()
.ConfigureServices(services =>
{
services.AddApplicationInsightsTelemetryWorkerService();
services.ConfigureFunctionsApplicationInsights();
// ... other registrations
})
.Build();Add the following key to local.settings.json for local telemetry (optional but recommended for debugging):
{
"IsEncrypted": false,
"Values": {
"APPLICATIONINSIGHTS_CONNECTION_STRING": "InstrumentationKey=<key>;IngestionEndpoint=https://<region>.in.applicationinsights.azure.com/"
}
}
⚠️ The key is already included insrc/local.settings.json.example. See #29 for the full settings template.
- Execution Count: Number of function executions
- Success Rate: % of successful executions
- Average Duration: Average execution time
- AI Token Usage: OpenAI token consumption
All queries below are verified against the Azure Functions v4 isolated worker table schema (requests, traces, dependencies).
// Executions last 24h
requests
| where timestamp > ago(24h)
| where name == "XPosterFunction"
| summarize count() by bin(timestamp, 1h)
| render timechart
// Error rate (severity >= 3 = Warning+)
traces
| where timestamp > ago(7d)
| where severityLevel >= 3
| summarize errorCount = count() by bin(timestamp, 1d)
| render barchart
// AI Cost Tracking
dependencies
| where timestamp > ago(30d)
| where target contains "openai"
| extend tokenUsage = toint(customDimensions.tokenCount)
| summarize totalTokens = sum(tokenUsage), totalCost = sum(tokenUsage) * 0.00006💡 Tip: To pin any query result to an Azure Dashboard, run it in the Logs blade, click the Pin to dashboard icon (📌) in the top-right corner of the results panel, choose your dashboard, and click Pin.
Application Insights Live Metrics streams telemetry in near real-time with sub-second latency — useful to verify the function is behaving correctly during local development.
- Start the function locally:
cd src func start - In the Azure Portal, open your Application Insights resource
- Click Live Metrics in the left-hand menu
- Trigger a function execution (timer fires automatically, or use an HTTP trigger)
- Observe incoming requests, dependency calls, exceptions, and custom traces in real time
ℹ️ Live Metrics works even in local development as long as
APPLICATIONINSIGHTS_CONNECTION_STRINGis set inlocal.settings.json.
The following example creates an alert for more than 3 consecutive errors within 1 hour:
- In the Azure Portal, navigate to your Application Insights resource
- Select Alerts → + Create → Alert rule
- Scope: confirm it points to the Application Insights resource
- Condition:
- Click + Add condition
- Signal type: Custom log search
- Enter the following KQL query:
traces | where severityLevel >= 3 | where timestamp > ago(1h) | summarize errorCount = count()
- Alert logic: Greater than threshold 3
- Evaluation frequency:
5 minutes - Lookback period:
1 hour
- Actions:
- Click + Add action group → Create action group
- Add a notification: type Email/SMS/Push/Voice, fill in your email
- Optionally add a Webhook action (e.g. to a Slack/Teams incoming webhook URL)
- Details:
- Severity: 2 – Warning
- Alert rule name:
XPoster - Consecutive Errors
- Click Review + Create
| Alert | KQL signal | Threshold | Severity |
|---|---|---|---|
| Consecutive errors | traces | where severityLevel >= 3 |
> 3 in 1h | Sev 2 – Warning |
| Token budget exceeded | dependencies | where target contains "openai" | extend t = toint(customDimensions.tokenCount) | summarize sum(t) |
> monthly budget | Sev 2 – Warning |
| High latency | requests | where name == "XPosterFunction" | summarize avg(duration) |
> 60 000 ms | Sev 3 – Informational |
| Function downtime | Built-in Availability test on the Function App URL | < 100% | Sev 1 – Error |
Use the following Bicep snippet to provision the consecutive-errors alert rule as Infrastructure-as-Code:
resource consecutiveErrorsAlert 'Microsoft.Insights/scheduledQueryRules@2022-06-15' = {
name: 'XPoster-ConsecutiveErrors'
location: resourceGroup().location
properties: {
description: 'Fires when more than 3 errors are logged within 1 hour'
severity: 2
enabled: true
scopes: [
appInsights.id
]
evaluationFrequency: 'PT5M'
windowSize: 'PT1H'
criteria: {
allOf: [
{
query: 'traces | where severityLevel >= 3 | summarize errorCount = count()'
timeAggregation: 'Count'
operator: 'GreaterThan'
threshold: 3
failingPeriods: {
numberOfEvaluationPeriods: 1
minFailingPeriodsToAlert: 1
}
}
]
}
actions: {
actionGroups: [
actionGroup.id
]
}
}
}📖 Full KQL queries, alert thresholds, and live debugging instructions: docs/monitoring.md.
- Azure Function setup
- Multi-platform sender architecture
- AI integration (gpt-4.1-nano, gpt-image-1.5)
- Twitter/X publishing
- LinkedIn publishing
- RSS feed parsing
- CI/CD pipeline
- AI migration to Azure Foundry
- Linkedin auto-update authorization token
- Configuration externalization
- Enhanced error handling
- Comprehensive testing (80%+ coverage)
- Post-publication analytics
- ML-based optimal timing
- Sentiment analysis
- A/B testing framework
- Trending hashtag detection
- Multi-language support
- Web based UI
- Real-time analytics
- Manual post scheduling
- Content calendar
- Performance metrics
- Mobile app (MAUI)
- Instagram publishing (complete setup)
- Threads (Meta) integration
- Mastodon support
- BlueSky protocol
- YouTube Shorts
- Podcast automation
Contributions, issues, and feature requests are welcome!
- Fork the project
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
- Follow C# (.NET) coding conventions
- Add unit tests for new features
- Update documentation
- Keep commits atomic and descriptive
- Respect existing design patterns
// ✅ Good
public async Task<Post> GenerateAsync()
{
var summary = await _aiService.GetSummaryAsync(content, maxLength);
if (string.IsNullOrWhiteSpace(summary))
{
_logger.LogWarning("Empty summary generated");
return null;
}
return new Post { Content = summary };
}
// ❌ Avoid
public async Task<Post> GenerateAsync() {
var summary = await _aiService.GetSummaryAsync(content, maxLength);
if (summary == null || summary == "") return null;
return new Post { Content = summary };
}This project is licensed under the MIT License. See the LICENSE file for details.
MIT License
Copyright (c) 2025 Marco Cavallo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
Marco Cavallo
- 🌐 Website: xposter.artcava.net
- 💼 LinkedIn: Marco Cavallo
- 🐦 Twitter: @artcava
- 📧 Email: cavallo.marco@gmail.com
- 🏢 Location: Turin, Italy
- Azure Functions - Serverless platform
- OpenAI - AI models (gpt-4.1-nano, gpt-image-1.5)
- LinqToTwitter - Twitter API wrapper
- .NET Foundation - Framework and community
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Email: cavallo.marco@gmail.com
If you find this project useful, consider leaving a ⭐ on GitHub!
Made with ❤️ in Turin, Italy
🏠 Homepage • 📖 Documentation • 🐛 Report Bug • 💡 Request Feature