Publishing a great blog post is only half the job — getting eyes on it is the other half. Manually copying a title, writing a caption, and posting it to Twitter/X, LinkedIn, and Facebook every single time you publish is repetitive, error-prone, and easy to forget. This is exactly the kind of task that should be automated.
In this guide, we'll build a lightweight automated blog-sharing pipeline in .NET Core that detects new blog posts and automatically shares them across social media platforms.
Why Automate Blog Sharing?
- Consistency – Every post gets promoted, every time, with no manual step skipped.
- Speed – Content goes live on social channels within minutes of publishing.
- Scalability – Works whether you publish once a week or fifty times a day.
- Multi-platform reach – One trigger, many destinations (Twitter/X, LinkedIn, Facebook, even Slack/Discord).
- Reduced human error – No more mismatched links, missing hashtags, or forgotten posts.
Architecture Overview
A simple, reliable automation pipeline looks like this:
[Blog CMS / RSS Feed]
│
▼
[Trigger: Webhook or Scheduled Poller]
│
▼
[.NET Core Worker Service]
│
┌────┼────────────┐
▼ ▼ ▼
[Twitter/X API] [LinkedIn API] [Facebook Graph API]
Two common trigger strategies:
- Webhook-based – Your CMS (WordPress, Ghost, custom CMS) calls a
.NET CoreWeb API endpoint the moment a post is published. - Poll-based (RSS) – A background job checks your blog's RSS/Atom feed every few minutes for new entries. This works even if your CMS has no webhook support.
We'll build the poll-based approach since it works with almost any blogging platform, then show how to swap in a webhook trigger.
Step 1: Set Up the .NET Core Project
dotnet new worker -n BlogSocialAutomation
cd BlogSocialAutomation
dotnet add package Hangfire.Core
dotnet add package Hangfire.MemoryStorage
dotnet add package System.ServiceModel.Syndication
We're using a Worker Service because this automation runs in the background with no UI, and Hangfire to handle reliable, recurring job scheduling (with retry support built in).
Step 2: Poll the RSS Feed for New Posts
public class BlogFeedService
{
private readonly HttpClient _httpClient;
private readonly string _feedUrl = "https://yourblog.com/rss";
public BlogFeedService(HttpClient httpClient) => _httpClient = httpClient;
public async Task<List<BlogPost>> GetLatestPostsAsync(DateTime since)
{
using var xmlReader = XmlReader.Create(await _httpClient.GetStreamAsync(_feedUrl));
var feed = SyndicationFeed.Load(xmlReader);
return feed.Items
.Where(item => item.PublishDate.UtcDateTime > since)
.Select(item => new BlogPost
{
Title = item.Title.Text,
Url = item.Links.First().Uri.ToString(),
PublishedAt = item.PublishDate.UtcDateTime,
Summary = item.Summary?.Text ?? string.Empty
})
.ToList();
}
}
Step 3: Post to Twitter/X (API v2)
public class TwitterPublisher
{
private readonly HttpClient _httpClient;
public TwitterPublisher(HttpClient httpClient, string bearerToken)
{
_httpClient = httpClient;
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", bearerToken);
}
public async Task PostTweetAsync(string text)
{
var payload = new { text };
var content = new StringContent(JsonSerializer.Serialize(payload),
Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(
"https://api.twitter.com/2/tweets", content);
response.EnsureSuccessStatusCode();
}
}
Note: Posting on behalf of a user requires OAuth 1.0a user-context authentication for the free tier, or OAuth 2.0 with the appropriate scopes on paid tiers. Store tokens securely (e.g., Azure Key Vault or user secrets — never hardcode them).
Step 4: Post to LinkedIn
public class LinkedInPublisher
{
private readonly HttpClient _httpClient;
private readonly string _authorUrn;
public LinkedInPublisher(HttpClient httpClient, string accessToken, string authorUrn)
{
_httpClient = httpClient;
_authorUrn = authorUrn;
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
}
public async Task ShareArticleAsync(string title, string url)
{
var payload = new
{
author = _authorUrn,
lifecycleState = "PUBLISHED",
specificContent = new Dictionary<string, object>
{
["com.linkedin.ugc.ShareContent"] = new
{
shareCommentary = new { text = $"New post: {title}" },
shareMediaCategory = "ARTICLE",
media = new[] { new { status = "READY", originalUrl = url } }
}
},
visibility = new Dictionary<string, object>
{
["com.linkedin.ugc.MemberNetworkVisibility"] = "PUBLIC"
}
};
var content = new StringContent(JsonSerializer.Serialize(payload),
Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(
"https://api.linkedin.com/v2/ugcPosts", content);
response.EnsureSuccessStatusCode();
}
}
Step 5: Post to Facebook (Graph API)
public class FacebookPublisher
{
private readonly HttpClient _httpClient;
private readonly string _pageId;
private readonly string _pageAccessToken;
public FacebookPublisher(HttpClient httpClient, string pageId, string pageAccessToken)
{
_httpClient = httpClient;
_pageId = pageId;
_pageAccessToken = pageAccessToken;
}
public async Task PostLinkAsync(string message, string link)
{
var url = $"https://graph.facebook.com/v19.0/{_pageId}/feed" +
$"?message={Uri.EscapeDataString(message)}" +
$"&link={Uri.EscapeDataString(link)}" +
$"&access_token={_pageAccessToken}";
var response = await _httpClient.PostAsync(url, null);
response.EnsureSuccessStatusCode();
}
}
Step 6: Orchestrate Everything with Hangfire
public class BlogSharingJob
{
private readonly BlogFeedService _feedService;
private readonly TwitterPublisher _twitter;
private readonly LinkedInPublisher _linkedIn;
private readonly FacebookPublisher _facebook;
private readonly IPostTracker _tracker; // Tracks last-shared timestamp
public BlogSharingJob(BlogFeedService feedService, TwitterPublisher twitter,
LinkedInPublisher linkedIn, FacebookPublisher facebook, IPostTracker tracker)
{
_feedService = feedService;
_twitter = twitter;
_linkedIn = linkedIn;
_facebook = facebook;
_tracker = tracker;
}
public async Task RunAsync()
{
var lastChecked = await _tracker.GetLastCheckedAsync();
var newPosts = await _feedService.GetLatestPostsAsync(lastChecked);
foreach (var post in newPosts)
{
var caption = $"📝 New blog post: {post.Title}\n{post.Url} #blog #tech";
await _twitter.PostTweetAsync(caption);
await _linkedIn.ShareArticleAsync(post.Title, post.Url);
await _facebook.PostLinkAsync(post.Title, post.Url);
}
await _tracker.UpdateLastCheckedAsync(DateTime.UtcNow);
}
}
Register a recurring job in Program.cs:
RecurringJob.AddOrUpdate<BlogSharingJob>(
"share-new-blog-posts",
job => job.RunAsync(),
Cron.Minutely);
This checks for new posts every minute and pushes them to all three platforms automatically.
Step 7: Handle Errors and Rate Limits Gracefully
Social APIs throttle aggressively, so wrap each publish call with retry logic (Polly is a great fit):
dotnet add package Polly
var retryPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
await retryPolicy.ExecuteAsync(() => _twitter.PostTweetAsync(caption));
Also log failures to a database or Application Insights so a failed post to one platform doesn't silently vanish.
Bonus: Switching to a Webhook Trigger
If your CMS supports webhooks (e.g., WordPress via a plugin, or a headless CMS like Contentful/Strapi), expose a minimal API endpoint instead of polling:
app.MapPost("/webhook/new-post", async (BlogPost post, BlogSharingJob job) =>
{
await job.ShareSinglePostAsync(post);
return Results.Ok();
});
This eliminates polling delay entirely — posts go live on social media within seconds of publishing.
Best Practices
- Never hardcode API keys — use
appsettings.jsonwith user secrets locally and Azure Key Vault / AWS Secrets Manager in production. - Idempotency — track already-shared post URLs so a job re-run doesn't double-post.
- Custom captions per platform — Twitter/X favors short, hashtag-driven text; LinkedIn favors a professional tone; Facebook allows longer descriptions.
- Monitor job health — use the Hangfire dashboard (
app.UseHangfireDashboard()) to track job success/failure history. - Respect API rate limits — batch posts and add delays if publishing many articles at once.
Conclusion
With just a handful of C# classes and a Hangfire recurring job, you can turn a manual, easy-to-forget task into a fully automated pipeline that shares every new blog post across Twitter/X, LinkedIn, and Facebook — reliably and consistently. From here, you can extend the system to Instagram, Discord, Slack, or even generate AI-written captions using the Anthropic API for more engaging social copy.