How to Automate Blog Sharing on Social Media Using .NET Core


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:

  1. Webhook-based – Your CMS (WordPress, Ghost, custom CMS) calls a .NET Core Web API endpoint the moment a post is published.
  2. 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.json with 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.

0 Comments Report