I Built My Own LinkedIn Ghostwriter Agent With Cloudflare, Inngest, and Claude

10 min read
Cover Image for I Built My Own LinkedIn Ghostwriter Agent With Cloudflare, Inngest, and Claude

I built a LinkedIn content agent that researches trending topics, reads my raw notes, writes in my voice, and sends me three posts scheduled for the week. I go through them, edit if needed, and approve them for specific days before anything goes live.

Why I Built It

My reason for posting on LinkedIn was simple: build a brand, get visibility, grow an audience.

I already had an app to schedule posts. All I did was write my ideas, experiences, and thoughts into it. But I kept running into the same problem: some weeks I had no idea what to post about, so I used a separate app just to get ideas.

Two apps, manual writing, and separate scheduling. It was friction I didn't need.

So I decided to build my own personal semi-autonomous LinkedIn agent that acts as my ghostwriter, one app that does everything: gives me ideas, schedules posts, and writes in my voice. But it still needs my edit and approval before anything goes to LinkedIn. That human-in-the-loop step was intentional. It gives it that human touch, which is exactly why it is not fully autonomous.

How It Actually Works

Every Sunday night at 9:35pm UTC the agent wakes up on its own. Here is what happens from that point:

  1. It pulls my latest notes from the idea vault — raw thoughts I have been dumping in throughout the week

  2. It fetches my resource list — URLs I have saved from articles and newsletters I want to stay on top of, crawls each one, and pulls the actual articles from them

  3. It hits the Hacker News Algolia API and finds the top trending stories from the last 7 days in my niche: Cloudflare Workers, serverless AI, Neon Postgres, Workers AI. It crawls those articles too

  4. All of that raw content — my notes, my resources, the trending HN articles — gets sent through Workers AI (Llama 3.1 8B) first. Cheap model, does the grunt work, strips out the noise and produces a clean summary

  5. Claude takes that summary and writes three LinkedIn posts, each from a different angle: a personal story, a tactical framework, and an opinion take on something trending

  6. The three drafts get saved to the database, scheduled for Monday, Wednesday, and Friday at 10am UTC

  7. Resend fires me an email saying my drafts are ready

  8. It can also help generate comments

Then I go to my approval dashboard, read through each one, edit anything I want to change, and hit approve. That approval fires an event into Inngest, which has been sleeping and waiting. It wakes up, waits until the exact scheduled time, then posts directly to LinkedIn via the LinkedIn API.

That last part, which is Inngest sleeping until the right time, was one of the more interesting things to build. It is not a cron. It is a durable step that can wait indefinitely. I approve a post on Sunday night, and Inngest just sits there until Monday 10 am, then fires. No polling, no extra infrastructure.

The Stack

As someone familiar with a lot of tech stacks, I went with a lightweight modern serverless setup that made building this genuinely enjoyable.

Cloudflare Workers + Hono — HTTP API layer. Dead simple for lightweight HTTP tasks. Deploys in seconds with zero cold starts. I use it as the action handler for fetching notes, generating posts, and everything else.

Inngest — Orchestration Inngest is an event-driven, durable execution platform. You write functions in TypeScript, Python, or Go to power background and scheduled jobs, with steps built in. No queues, no infra, no state management headaches.

For an agent like this, I needed an orchestrator that could handle parallel async steps with retry logic. In AWS, we have Step Functions, but Step Functions is too heavy for something personal that only I log into. Cloudflare Cron was another option, but it has a 30-second execution limit and cannot pause mid-run. Inngest has durable steps, sleeps, and can wait indefinitely for human approval. That last part was exactly what I needed.

Neon — Database Neon is a serverless Postgres. I love it. I use it to store Notes (idea vault entries), Resources (URL list), Drafts (generated posts and comments), and Post log (history of everything posted)

Workers AI (Llama 3.1 8B) — Cheap preprocessing. I wanted Claude to handle only the actual writing. So Workers AI goes through my resources and the trending HN articles first, produces a summary, and Claude uses that summary to generate the post. Cheap models do the grunt work; Claude does the quality work. This kept the Claude API costs low since it only runs once a week and only sees clean summarised content, not raw HTML.

Claude API (claude-sonnet-4-5) Handles the actual writing.

SST — Infrastructure and deployment SST is an open-source framework that makes it easy to build serverless applications on your own infrastructure. It is my IaC and deployment layer: the glue holding everything together. It deploys everything and manages the environment. You can use it with AWS or Cloudflare. I picked it because I wanted to build something fast for personal use without getting bogged down in infrastructure complexity.

React + Vite — Lighter bundle, works perfectly with the Cloudflare Workers API.

Resend — Email notifications when drafts are ready.

LinkedIn Share API — Posting.

What Does It Actually Cost

Almost nothing. Most of this runs on free tiers:

  • Cloudflare Workers — free tier, more than enough for personal use

  • Neon — free tier handles the data volume easily

  • Inngest — free tier covers the once-a-week run comfortably

  • Resend — free tier, one email a week

  • Workers AI — free tier, a handful of summarisation calls per week

The only real running cost is the Claude API. Since it runs once a week and only processes clean summaries, not raw content, a single Sunday run costs a few cents at most.


Step by Step: How I Set It Up

I will walk you through the key parts. The finished code and full setup instructions are linked at the end.

1. Scaffold the project with SST

npx sst@latest init

SST will detect you have no frontend and use the vanilla template. Then scaffold the Vite React app:

npm create vite@latest frontend -- --template react-ts

Update sst.config.ts to deploy it to Cloudflare Pages:

async run() {
  new sst.cloudflare.StaticSite("Frontend", {
    path: "frontend",
    build: {
      command: "npm run build",
      output: "dist",
    },
  });
}

2. Add the API Worker

Create packages/api/ with:

  • package.json — dependencies: hono, wrangler, @cloudflare/workers-types

  • tsconfig.json — TypeScript config targeting ESNext with Cloudflare Workers types

  • src/index.ts — the Worker with Hono, stub routes for all modules, CORS enabled

Update sst.config.ts to add the Worker resource and pass its URL to the frontend as VITE_API_URL.

3. Set up Neon

Create a project on neon.tech. Go to your Neon dashboard → SQL Editor and run the schema to create the tables: notes, resources, drafts, post_log, linkedin_tokens.

4. Connect the database

SST stores secrets inside your Cloudflare account. To do that it needs a Cloudflare API token. Get one from: Cloudflare Dashboard → My Profile → API Tokens → Create Token → use the "Edit Cloudflare Workers" template.

export CLOUDFLARE_API_TOKEN=your-token-here

You also need to enable R2 (Cloudflare's object storage, like S3). SST uses it to store infrastructure state. Free tier is generous — 10GB free — but requires a card on file.

Now set your database secret:

npx sst secret set DatabaseUrl "your-neon-connection-string"

Update sst.config.ts to link it to the Worker:

async run() {
  const databaseUrl = new sst.Secret("DatabaseUrl");
  const api = new sst.cloudflare.Worker("Api", {
    url: true,
    handler: "packages/api/src/index.ts",
    environment: {
      DATABASE_URL: databaseUrl.value,
    },
  });
}

Install the Neon driver:

cd packages/api && npm install @neondatabase/serverless

5. Wire up Inngest

Go to the Inngest Dashboard → Settings → Event Keys and copy your Event Key. Then Settings → Signing Keys for the Signing Key.

npx sst secret set InngestSigningKey "your-signing-key"
npx sst secret set InngestEventKey "your-event-key"
npx sst deploy

6. Set the remaining secrets

The Worker needs several more API keys. Set them all before deploying:

npx sst secret set AnthropicApiKey "your-anthropic-api-key"
npx sst secret set ResendApiKey "your-resend-api-key"
npx sst secret set NotifyEmail "your@email.com"
npx sst secret set LinkedinClientId "your-linkedin-client-id"
npx sst secret set LinkedinClientSecret "your-linkedin-client-secret"
npx sst secret set CloudflareAccountId "your-cloudflare-account-id"
npx sst secret set CloudflareApiToken "your-cloudflare-api-token"

For the LinkedIn credentials: go to LinkedIn Developers, create an app, and copy the Client ID and Client Secret. Make sure you add w_member_social and openid profile to the OAuth scopes.

For Workers AI: the CloudflareAccountId is on your Cloudflare dashboard home page. The CloudflareApiToken is the same one you created in step 4 — you can reuse it.

Now deploy everything:

npx sst deploy

SST will output your Worker URL and your Cloudflare Pages URL when it is done.

7. Connect LinkedIn

This is the one manual step you cannot skip. Visit your deployed Worker URL in a browser:

https://your-worker-url.dev/auth/linkedin

It will redirect you to LinkedIn to authorize the app. Once you approve, it stores the access token in your database. Without this the poster will fail when it tries to publish.

One thing to know: the LinkedIn access token expires every 60 days. There is no automatic refresh. So every 60 days you visit that URL again and re-authorize. It takes 30 seconds but you have to remember to do it.

8. Test the agent without waiting for Sunday

Once deployed you do not have to wait until Sunday night to see if everything works. Go to your Inngest dashboard, find the PAUL Agent function, and send a test paul/agent.run event manually. It will run through all the steps — fetch notes, fetch resources, hit HN, summarize, write posts, save drafts, and send the email. Watch the steps execute in the Inngest dashboard in real time. If anything fails you will see exactly which step and why.


What Is Still Missing

I want to be honest , this is not fully finished. i want to connect it to notion page, a page that contain all your previous post, so the agent can better understand your writing voice. i also want to add other social media like bluesky and twitter.

If you want to build something similar, the stack is genuinely enjoyable to work with. Lightweight, fast to deploy, and the pieces fit together in a way that makes sense.

If this guide helped you follow for more projects!

That’s all for this post. See you in my next post.

The full source code is here on GitHub. Feel free to star it, fork it, and adapt it for your own setup

Support me- buymeacoffee.com/obayuwana

Connect with me on LinkedIn: LinkedIn Profile