<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kjetil Furås</title>
    <description>The latest articles on DEV Community by Kjetil Furås (@kfuras).</description>
    <link>https://dev.to/kfuras</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3806150%2F9172817a-2f98-47b2-a39c-d3c8d672a84d.jpg</url>
      <title>DEV Community: Kjetil Furås</title>
      <link>https://dev.to/kfuras</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kfuras"/>
    <language>en</language>
    <item>
      <title>Best Markdown Editors for WordPress Publishing in 2026</title>
      <dc:creator>Kjetil Furås</dc:creator>
      <pubDate>Sun, 19 Apr 2026 18:14:43 +0000</pubDate>
      <link>https://dev.to/kfuras/best-markdown-editors-for-wordpress-publishing-in-2026-1o30</link>
      <guid>https://dev.to/kfuras/best-markdown-editors-for-wordpress-publishing-in-2026-1o30</guid>
      <description>&lt;p&gt;Compare the best markdown editors that publish to WordPress — Notipo, Ulysses, iA Writer, MarsEdit, and Jetpack. Features, pricing, and which one fits your workflow.&lt;/p&gt;

&lt;p&gt;If you write in markdown and publish to WordPress, the Gutenberg editor is not your friend. It's a block editor designed for drag-and-drop page building, not for writing long-form content in plain text. Pasting markdown into Gutenberg is a guaranteed formatting headache.&lt;/p&gt;

&lt;p&gt;The good news is there are dedicated tools that let you write in markdown and publish to WordPress without touching Gutenberg. I've used most of them over the past few years. Here's how they compare.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to Look For
&lt;/h2&gt;

&lt;p&gt;Before the comparison, here are the features that actually matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Native WordPress publishing&lt;/strong&gt; — can it publish directly via the REST API, or do you need to copy-paste?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image handling&lt;/strong&gt; — does it upload images to your WordPress media library?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code block support&lt;/strong&gt; — does it convert fenced code blocks to syntax-highlighted blocks?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO metadata&lt;/strong&gt; — can you set focus keywords, meta descriptions, and titles?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Featured images&lt;/strong&gt; — does it handle featured image creation?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gutenberg output&lt;/strong&gt; — does it create proper Gutenberg blocks, or raw HTML?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Notipo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://notipo.com" rel="noopener noreferrer"&gt;Notipo&lt;/a&gt; is built specifically for the markdown-to-WordPress pipeline. Full disclosure: I built it, so I'm biased. But I built it because nothing else did what I needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Write in the browser-based markdown editor (or sync from Notion, or use the API). Notipo converts to Gutenberg blocks, uploads images to your media library, generates AI featured images, sets Rank Math SEO metadata, and publishes. One click.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code blocks:&lt;/strong&gt; Converted to Prismatic plugin blocks with automatic language detection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free (5 posts/month). Pro at $19/month for unlimited posts, AI featured images, and webhook triggers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Developers and technical writers who want the full pipeline automated — especially if your posts include code.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Ulysses
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; A polished writing app for Mac and iOS with built-in WordPress publishing. Write in a markdown-like syntax, organize with folders and tags, and publish directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code blocks:&lt;/strong&gt; Basic support. Standard HTML code blocks, not syntax-highlighted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image handling:&lt;/strong&gt; Uploads to WordPress media library. No AI generation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SEO:&lt;/strong&gt; No built-in SEO metadata support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; $5.99/month or $49.99/year. Apple platforms only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Writers who want a beautiful native Mac writing experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. iA Writer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; The gold standard for distraction-free markdown writing with WordPress publishing built in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code blocks:&lt;/strong&gt; Standard HTML code blocks. No syntax highlighting integration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image handling:&lt;/strong&gt; Uploads inline images. Featured images need to be set manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SEO:&lt;/strong&gt; None. Metadata is handled in WordPress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; One-time purchase — $49.99 (Mac), $29.99 (iOS/Android/Windows).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Writers who want a minimal, focused editor and handle SEO in WordPress.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. MarsEdit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Desktop blog editor for Mac. Connects to WordPress and lets you write and publish from a native app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code blocks:&lt;/strong&gt; Raw HTML.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Image handling:&lt;/strong&gt; Uploads to WordPress media library.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SEO:&lt;/strong&gt; Supports custom fields for SEO plugin integration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; $59.95 one-time. Mac only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Mac users who want a traditional desktop blogging client.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Jetpack
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Adds markdown support directly to the WordPress editor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code blocks:&lt;/strong&gt; Standard WordPress code blocks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SEO:&lt;/strong&gt; Separate Jetpack SEO tools available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free markdown module.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; People who want to stay inside WordPress but write in markdown.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Notipo&lt;/th&gt;
&lt;th&gt;Ulysses&lt;/th&gt;
&lt;th&gt;iA Writer&lt;/th&gt;
&lt;th&gt;MarsEdit&lt;/th&gt;
&lt;th&gt;Jetpack&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Direct WP publish&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gutenberg blocks&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code highlighting&lt;/td&gt;
&lt;td&gt;Prismatic&lt;/td&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SEO metadata&lt;/td&gt;
&lt;td&gt;Rank Math&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Custom fields&lt;/td&gt;
&lt;td&gt;Separate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI featured images&lt;/td&gt;
&lt;td&gt;Yes (Pro)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Platform&lt;/td&gt;
&lt;td&gt;Web&lt;/td&gt;
&lt;td&gt;Apple&lt;/td&gt;
&lt;td&gt;All&lt;/td&gt;
&lt;td&gt;Mac&lt;/td&gt;
&lt;td&gt;WordPress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free tier&lt;/td&gt;
&lt;td&gt;5 posts/mo&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  My Recommendation
&lt;/h2&gt;

&lt;p&gt;If you're a developer or technical writer whose posts include code, &lt;a href="https://app.notipo.com/auth/register" rel="noopener noreferrer"&gt;Notipo&lt;/a&gt; is the obvious choice. Nothing else converts fenced code blocks to syntax-highlighted WordPress blocks automatically. And nothing else handles the full pipeline — images, SEO, featured images, and publishing — in one step.&lt;/p&gt;

&lt;p&gt;If you're a non-technical writer who wants a beautiful native app and doesn't need code highlighting, iA Writer or Ulysses are both excellent. iA Writer wins on pricing (one-time purchase), Ulysses wins on organization features.&lt;/p&gt;

&lt;p&gt;If you want to stay inside WordPress and just need markdown syntax, Jetpack's markdown module is free and works fine.&lt;/p&gt;

&lt;p&gt;The real question is: how much of the publishing pipeline do you want automated? If the answer is "all of it," there's only one option.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Kjetil Furås builds automations with AI agents and infrastructure as code. Follow along on &lt;a href="https://x.com/kjetilfuras" rel="noopener noreferrer"&gt;X&lt;/a&gt; or join the &lt;a href="https://www.skool.com/build-automate" rel="noopener noreferrer"&gt;Build &amp;amp; Automate community on Skool&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>markdown</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Write First, Sync Later: How Notipo Works Without Notion</title>
      <dc:creator>Kjetil Furås</dc:creator>
      <pubDate>Sun, 19 Apr 2026 18:14:07 +0000</pubDate>
      <link>https://dev.to/kfuras/write-first-sync-later-how-notipo-works-without-notion-3hk0</link>
      <guid>https://dev.to/kfuras/write-first-sync-later-how-notipo-works-without-notion-3hk0</guid>
      <description>&lt;p&gt;You don't need Notion to use Notipo. Write in the built-in markdown editor, and Notipo handles images, SEO, code highlighting, and WordPress publishing automatically.&lt;/p&gt;

&lt;p&gt;People keep asking me if they need Notion to use Notipo. The name doesn't help — "Notipo" sounds like it's all about Notion. Fair assumption, but no. You can use Notipo without ever opening Notion.&lt;/p&gt;

&lt;p&gt;Here's how the writing-first flow works, step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Connect Your WordPress Site
&lt;/h2&gt;

&lt;p&gt;After signing up, you add your WordPress site using an &lt;a href="https://notipo.com/blog/wordpress-application-passwords" rel="noopener noreferrer"&gt;application password&lt;/a&gt;. This takes about two minutes. Notipo verifies the connection and pulls in your existing categories and tags.&lt;/p&gt;

&lt;p&gt;That's the only setup. No Notion OAuth, no database template, no workspace configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Write in the Markdown Editor
&lt;/h2&gt;

&lt;p&gt;Click "New Post" and you get a clean markdown editor. No distractions, no sidebar, no block palette. Just a title field and a writing area.&lt;/p&gt;

&lt;p&gt;The editor supports standard markdown: headings, bold, italic, links, ordered and unordered lists, blockquotes, inline images, and fenced code blocks. If you've written a GitHub README, you already know the syntax.&lt;/p&gt;

&lt;p&gt;You can also set the category, tags, and SEO metadata (focus keyword, meta title, meta description) right from the editor. These fields are optional — Notipo will generate sensible defaults if you leave them blank.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Hit Publish
&lt;/h2&gt;

&lt;p&gt;When you're ready, click Publish. Here's what Notipo does behind the scenes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Markdown to Gutenberg&lt;/strong&gt; — your markdown is converted to proper WordPress Gutenberg blocks, not just raw HTML. This means your posts look native in the WordPress editor if you ever need to edit them there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image upload&lt;/strong&gt; — any images in your post are uploaded to your WordPress media library with permanent URLs. No expiring links, no broken images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Featured image&lt;/strong&gt; — if you have AI featured images enabled (Pro), Notipo generates a branded 1200×628 image based on your title and category.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code highlighting&lt;/strong&gt; — fenced code blocks are converted to Prismatic plugin blocks with automatic language detection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO metadata&lt;/strong&gt; — Notipo writes your focus keyword, meta title, and meta description directly to Rank Math via its REST API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entire process takes about 10 seconds. You get a link to the live WordPress post when it's done.&lt;/p&gt;

&lt;h2&gt;
  
  
  What About Drafts?
&lt;/h2&gt;

&lt;p&gt;You don't have to publish immediately. Save as draft and the post sits in your Notipo dashboard. You can preview, edit, and publish when you're ready. You can also publish to WordPress as a draft first — it creates the post with "Draft" status so you can preview it on your actual site before going live.&lt;/p&gt;

&lt;h2&gt;
  
  
  The API Route
&lt;/h2&gt;

&lt;p&gt;There's a third way to get content into Notipo: the API. Send a POST request with your markdown content, title, and metadata, and Notipo creates and publishes the post. This is how &lt;a href="https://notipo.com/blog/openclaw-wordpress-publishing" rel="noopener noreferrer"&gt;AI agents publish through Notipo&lt;/a&gt; — one HTTP request handles everything.&lt;/p&gt;

&lt;p&gt;So the full picture is three input methods:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Markdown editor&lt;/strong&gt; — write directly in the browser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notion sync&lt;/strong&gt; — connect your workspace and publish by changing a status&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API&lt;/strong&gt; — send content programmatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All three feed into the same pipeline. Same image handling, same SEO, same code highlighting, same WordPress publishing. The only difference is where the content starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Add Notion Later
&lt;/h2&gt;

&lt;p&gt;The "sync later" part of the title is real. Some users start with the editor, get comfortable with the publishing pipeline, and then connect Notion when they want a more structured editorial workflow — databases, properties, status-based publishing, team collaboration.&lt;/p&gt;

&lt;p&gt;Notion sync is great if you already use Notion for content planning. But it's not a prerequisite. You can publish hundreds of posts through the editor and never touch Notion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Now
&lt;/h2&gt;

&lt;p&gt;The free plan gives you 5 posts per month with the markdown editor, code highlighting, and SEO metadata. &lt;a href="https://app.notipo.com/auth/register" rel="noopener noreferrer"&gt;Sign up here&lt;/a&gt; — you can publish your first post in about five minutes.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Kjetil Furås builds automations with AI agents and infrastructure as code. Follow along on &lt;a href="https://x.com/kjetilfuras" rel="noopener noreferrer"&gt;X&lt;/a&gt; or join the &lt;a href="https://www.skool.com/build-automate" rel="noopener noreferrer"&gt;Build &amp;amp; Automate community on Skool&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>markdown</category>
      <category>webdev</category>
      <category>writing</category>
    </item>
    <item>
      <title>Why We Built a Markdown Editor for WordPress</title>
      <dc:creator>Kjetil Furås</dc:creator>
      <pubDate>Sun, 19 Apr 2026 18:13:35 +0000</pubDate>
      <link>https://dev.to/kfuras/why-we-built-a-markdown-editor-for-wordpress-5hk8</link>
      <guid>https://dev.to/kfuras/why-we-built-a-markdown-editor-for-wordpress-5hk8</guid>
      <description>&lt;p&gt;60% of Notipo signups never finished onboarding because Notion was required. So I built a markdown editor that publishes to WordPress directly — no Notion needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data That Changed Everything
&lt;/h2&gt;

&lt;p&gt;When I launched Notipo, the pitch was simple: connect your Notion workspace, change a status, and your post publishes to WordPress automatically. Images, SEO metadata, code highlighting — all handled. I thought the Notion integration was the product.&lt;/p&gt;

&lt;p&gt;Turns out, I was wrong.&lt;/p&gt;

&lt;p&gt;A few weeks after launch, I opened PostHog and looked at the funnel. People were signing up — that part was working. But 60% of them never finished onboarding. They'd create an account, see the "Connect your Notion workspace" step, and leave.&lt;/p&gt;

&lt;p&gt;At first I assumed the OAuth flow was confusing. Maybe the Notion permissions screen was scaring people off. So I simplified the copy, added a video walkthrough, made the button bigger. Nothing changed.&lt;/p&gt;

&lt;p&gt;Then I started talking to users who did make it through. The pattern was obvious: the people who loved Notipo were already heavy Notion users. They had databases, templates, editorial calendars. For them, connecting Notion was natural.&lt;/p&gt;

&lt;p&gt;But the people who bounced? They wanted to write and publish to WordPress. That's it. They didn't use Notion. They didn't want to learn Notion. They just wanted a clean writing experience that ended with a published WordPress post.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wrong Assumption
&lt;/h2&gt;

&lt;p&gt;I'd built Notipo around my own workflow. I write in Notion because I like it — the block editor, the databases, the properties. For me, the Notion-to-WordPress pipeline is the natural way to publish. I assumed everyone else felt the same way.&lt;/p&gt;

&lt;p&gt;That assumption filtered into everything: the landing page led with "Notion to WordPress," the onboarding required a Notion connection before you could do anything, and the blog content was entirely about Notion workflows. If you weren't a Notion user, the whole product felt like it wasn't for you.&lt;/p&gt;

&lt;p&gt;The real value of Notipo was never the Notion integration. It was everything that happens &lt;em&gt;after&lt;/em&gt; you write — the automatic image handling, the SEO metadata, the code syntax highlighting, the one-click WordPress publishing. The Notion connection was just one way to get content in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Editor
&lt;/h2&gt;

&lt;p&gt;So I built a markdown editor directly into Notipo. The goal was straightforward: give people a clean place to write that feeds into the same publishing pipeline — without requiring any external tools.&lt;/p&gt;

&lt;p&gt;You open the editor, write your post in markdown, add a title and category, and hit publish. Notipo converts the markdown to WordPress Gutenberg blocks, uploads any images to your media library, generates a featured image if you have that enabled, sets your Rank Math SEO metadata, and publishes. Same pipeline as the Notion flow, different input.&lt;/p&gt;

&lt;p&gt;The editor supports everything you'd expect: headings, lists, bold/italic, links, inline images, and fenced code blocks with language detection. It's not trying to be VS Code or Notion — it's a focused writing tool that gets out of the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notion Didn't Go Away
&lt;/h2&gt;

&lt;p&gt;The Notion integration is still there and it's still great. If you write in Notion, you can still connect your workspace and publish by changing a status. Nothing about that workflow changed.&lt;/p&gt;

&lt;p&gt;What changed is the positioning. Notion is now a power-user add-on, not the core experience. The onboarding no longer requires a Notion connection. You can sign up, connect your WordPress site, and publish your first post in under five minutes — all without ever hearing the word "Notion."&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;The biggest lesson was that your own workflow is a terrible proxy for your users' workflow. I used Notion, so I assumed everyone did. The data said otherwise.&lt;/p&gt;

&lt;p&gt;The second lesson was that the core value of a product isn't always where you think it is. I thought the value was "Notion to WordPress." The actual value was "write once, publish to WordPress with everything handled." The input method — Notion, markdown editor, API — was just a detail.&lt;/p&gt;

&lt;p&gt;If you're building a product and your onboarding has a step that makes 60% of users leave, don't try to optimize that step. Ask whether it should be required at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;The markdown editor is live now for all users, including the free plan. &lt;a href="https://app.notipo.com/auth/register" rel="noopener noreferrer"&gt;Sign up&lt;/a&gt;, connect your WordPress site, and publish your first post. No Notion required.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Kjetil Furås builds automations with AI agents and infrastructure as code. Follow along on &lt;a href="https://x.com/kjetilfuras" rel="noopener noreferrer"&gt;X&lt;/a&gt; or join the &lt;a href="https://www.skool.com/build-automate" rel="noopener noreferrer"&gt;Build &amp;amp; Automate community on Skool&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>markdown</category>
      <category>webdev</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Publish Notion Pages to the Web (With SEO and Your Own Domain)</title>
      <dc:creator>Kjetil Furås</dc:creator>
      <pubDate>Sat, 18 Apr 2026 08:50:13 +0000</pubDate>
      <link>https://dev.to/kfuras/how-to-publish-notion-pages-to-the-web-with-seo-and-your-own-domain-2n9</link>
      <guid>https://dev.to/kfuras/how-to-publish-notion-pages-to-the-web-with-seo-and-your-own-domain-2n9</guid>
      <description>&lt;p&gt;Notion's "Publish to web" button is the fastest way to share a page publicly. It's also the worst way to publish content you want people to actually find. No custom domain. No meta tags. No sitemap. Images hosted on temporary S3 URLs that expire within an hour. Google can't reliably index it, and readers see a &lt;code&gt;notion.site&lt;/code&gt; URL instead of your brand.&lt;/p&gt;

&lt;p&gt;If you want to publish Notion pages as real web content — with your own domain, SEO metadata, permanent images, and analytics — you need a publishing layer between Notion and the web. WordPress is the most common choice, and &lt;a href="https://notipo.com" rel="noopener noreferrer"&gt;Notipo&lt;/a&gt; connects the two automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Notion's Built-in Publishing Falls Short
&lt;/h2&gt;

&lt;p&gt;Notion's native publish feature was designed for quick sharing, not for content that needs to rank or convert. Here's what's missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No custom domain&lt;/strong&gt; — every page lives on &lt;code&gt;yourworkspace.notion.site&lt;/code&gt;. You can't point your own domain at it without third-party workarounds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No SEO control&lt;/strong&gt; — no meta descriptions, no focus keywords, no canonical URLs. Google sees a generic Notion page with no optimization signals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temporary images&lt;/strong&gt; — Notion serves images from AWS S3 with signed URLs that expire after about one hour. Shared links, social previews, and cached pages all break once the URL rotates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No analytics&lt;/strong&gt; — no page views, no traffic sources, no conversion tracking. You're publishing blind.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No sitemap or RSS&lt;/strong&gt; — search engines have no structured way to discover your pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For internal docs or team wikis, none of this matters. For blog posts, landing pages, or any content meant to attract organic traffic, it's a dealbreaker.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Two Approaches to Publishing Notion Pages
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Notion as a headless CMS with a custom frontend
&lt;/h3&gt;

&lt;p&gt;Frameworks like Next.js or Astro can pull content from the Notion API and render it as static pages. This gives you full control over design and hosting, but you're building and maintaining a custom site. Every Notion block type needs a renderer. Image expiry needs a caching layer. SEO needs manual wiring. It works, but it's a development project, not a publishing workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Notion synced to WordPress
&lt;/h3&gt;

&lt;p&gt;WordPress already handles domains, SEO, sitemaps, analytics, and image hosting. The missing piece is getting content from Notion into WordPress without copy-pasting. A sync tool like Notipo bridges this gap — content flows from Notion to WordPress automatically, and WordPress handles everything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Notipo Publishes Notion Pages to WordPress
&lt;/h2&gt;

&lt;p&gt;The pipeline is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write in Notion&lt;/strong&gt; — use a database with properties for title, slug, category, tags, SEO keyword, and meta description.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connect Notion and WordPress&lt;/strong&gt; — Notipo connects to both via OAuth (Notion) and application passwords (WordPress). Setup takes about two minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish&lt;/strong&gt; — change the page status or trigger a sync. Notipo converts Notion blocks to Gutenberg blocks, downloads and re-uploads images to your WordPress media library (permanent URLs), writes SEO metadata to your plugin, and optionally generates a featured image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update&lt;/strong&gt; — edit the Notion page and re-sync. Notipo updates the existing WordPress post in place — same URL, same post ID, no duplicates.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What Gets Synced
&lt;/h2&gt;

&lt;p&gt;Every sync handles the full content stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Text and formatting&lt;/strong&gt; — headings, bold, italic, links, lists, blockquotes, callouts, toggles, dividers. Notion blocks map to Gutenberg blocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Images&lt;/strong&gt; — every image is downloaded from Notion's temporary URL and uploaded to your WordPress media library. The published post uses permanent WordPress URLs, not expiring S3 links.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code blocks&lt;/strong&gt; — language detection preserved. Pair with Highlight.js or Prism.js for syntax highlighting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO metadata&lt;/strong&gt; — focus keyword, SEO title, and meta description written directly to your SEO plugin. Notipo auto-detects Rank Math, Yoast SEO, SEOPress, and All in One SEO.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Featured images&lt;/strong&gt; — generated automatically from the post title, or use a cover image from Notion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Categories and tags&lt;/strong&gt; — mapped from Notion properties to WordPress taxonomies.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting Up Your Notion Database
&lt;/h2&gt;

&lt;p&gt;Your Notion database needs a few standard properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Title&lt;/strong&gt; — the page title (built-in Notion property)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slug&lt;/strong&gt; — text property for the URL path&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Category&lt;/strong&gt; — select property matching your WordPress categories&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tags&lt;/strong&gt; — multi-select property for WordPress tags&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO Keyword&lt;/strong&gt; — text property for the focus keyword&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meta Description&lt;/strong&gt; — text property for the meta description&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status&lt;/strong&gt; — select property (Draft, Review, Publish)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Publishing via the API or CLI
&lt;/h2&gt;

&lt;p&gt;Beyond the web dashboard, Notipo exposes a REST API and CLI for programmatic publishing. A single API call handles the entire pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://notipo.com/api/posts &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: your-api-key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "title": "My Notion Page",
    "body": "# Content in markdown",
    "category": "Blog",
    "seoKeyword": "publish notion pages",
    "slug": "my-notion-page"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API creates a Notion page, syncs it to WordPress, uploads images, writes SEO metadata, and generates a featured image — all in one request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notion Publishing vs. Other Approaches
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Notion native publish&lt;/strong&gt; — instant, no setup. But no SEO, no custom domain, broken images after an hour. Only useful for quick internal sharing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Super.so / Potion.so&lt;/strong&gt; — wraps Notion pages with a custom domain and some styling. Still depends on Notion's rendering, limited SEO control, and you're locked into their proxy layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom Next.js / Astro site&lt;/strong&gt; — maximum flexibility, but you're maintaining a codebase. Every new Notion block type needs a custom renderer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notipo + WordPress&lt;/strong&gt; — Notion stays your writing tool, WordPress handles publishing. Full SEO control, permanent images, 4 SEO plugins supported, featured image generation. No code to maintain.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations to Know
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One-way sync&lt;/strong&gt; — content flows from Notion to WordPress only. Edits in wp-admin don't sync back to Notion. Notion is the source of truth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WordPress required&lt;/strong&gt; — Notipo publishes to self-hosted WordPress sites. It doesn't work with WordPress.com free plans or non-WordPress platforms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Block coverage&lt;/strong&gt; — most Notion block types are supported. Databases-as-content and some advanced layouts may not map perfectly to Gutenberg blocks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can I publish Notion pages to the web?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Notion has a built-in "Publish to web" option, but it uses a notion.site subdomain with no SEO control, no custom domain, and temporary image URLs. To publish properly — with your own domain and permanent images — sync to WordPress using Notipo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do Notion published pages show up on Google?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Notion's native published pages are not reliably indexed. They lack meta descriptions, canonical URLs, sitemaps, and structured data. Publishing to WordPress through Notipo gives you full SEO control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there a free way to publish Notion pages to WordPress?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Notipo's free tier lets you publish up to 5 pages per month — including image caching, SEO metadata, and featured image generation. No credit card required. Pro is $19/month with a 7-day free trial.&lt;/p&gt;




&lt;p&gt;&lt;a href="https://notipo.com/auth/register" rel="noopener noreferrer"&gt;Create a free Notipo account&lt;/a&gt; and start publishing Notion pages to WordPress in about two minutes.&lt;/p&gt;

</description>
      <category>notion</category>
      <category>wordpress</category>
      <category>seo</category>
      <category>blogging</category>
    </item>
    <item>
      <title>Notion CMS: Can You Use Notion as a Content Management System?</title>
      <dc:creator>Kjetil Furås</dc:creator>
      <pubDate>Sat, 18 Apr 2026 08:45:32 +0000</pubDate>
      <link>https://dev.to/kfuras/notion-cms-can-you-use-notion-as-a-content-management-system-3c9l</link>
      <guid>https://dev.to/kfuras/notion-cms-can-you-use-notion-as-a-content-management-system-3c9l</guid>
      <description>&lt;p&gt;Notion has everything you'd expect from a content management system. Databases with custom properties. Rich text editing. Status columns for editorial workflows. Drag-and-drop media. Templates. Collaboration. It looks like a CMS, and many teams already use it as one.&lt;/p&gt;

&lt;p&gt;But Notion was built for internal docs and project management — not for publishing content to the web. The moment you need a custom domain, SEO control, permanent image URLs, or analytics, Notion stops being enough. The question isn't whether Notion can be a CMS. It's whether Notion can be &lt;em&gt;your&lt;/em&gt; CMS — and what you need to add to make it work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes Notion Work as a CMS
&lt;/h2&gt;

&lt;p&gt;Notion's database features map surprisingly well to CMS requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Databases as content types&lt;/strong&gt; — each database is a content collection. Blog posts, landing pages, documentation — one database per type, with custom properties for metadata.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Properties as fields&lt;/strong&gt; — title, slug, category, tags, author, publish date, SEO keyword, meta description. Every field a traditional CMS offers, you can create as a Notion property.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status workflows&lt;/strong&gt; — Draft, In Review, Published. Notion's status property gives you a basic editorial pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rich content editing&lt;/strong&gt; — headings, images, code blocks, callouts, tables, toggles, embeds. Notion's block editor handles most content types.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time collaboration&lt;/strong&gt; — multiple writers and editors working on the same page simultaneously, with comments and mentions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Templates&lt;/strong&gt; — create a blog post template with pre-filled properties and placeholder content. Every new post starts from a consistent structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For content creation and editorial management, Notion is genuinely excellent. Most dedicated CMS platforms have worse editing experiences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Notion Falls Short as a CMS
&lt;/h2&gt;

&lt;p&gt;The gap appears when you try to publish. A CMS needs to serve content to readers, and Notion wasn't designed for that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No custom domains&lt;/strong&gt; — Notion's "Share to Web" publishes to &lt;code&gt;notion.site&lt;/code&gt; subdomains. You can't serve content from your own domain without a third-party wrapper.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No SEO control&lt;/strong&gt; — no meta descriptions, no focus keywords, no Open Graph tags, no structured data. Google sees your content but can't rank it properly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temporary image URLs&lt;/strong&gt; — Notion stores images on Amazon S3 with signed URLs that expire after about one hour. Any system that embeds Notion image URLs directly will show broken images within hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No sitemap&lt;/strong&gt; — search engines rely on sitemaps to discover content. Notion doesn't generate one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No analytics&lt;/strong&gt; — no Google Analytics, no Search Console, no conversion tracking. You can't measure what's working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No page speed optimization&lt;/strong&gt; — Notion pages are JavaScript-heavy SPAs. Core Web Vitals scores are poor compared to server-rendered HTML.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No plugins or extensions&lt;/strong&gt; — WordPress has 60,000+ plugins for forms, e-commerce, membership, caching. Notion has none of this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't minor gaps. They're the difference between a content editor and a publishing platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Use Notion as a Headless CMS for WordPress
&lt;/h2&gt;

&lt;p&gt;The term "headless CMS" means separating where you create content from where you display it. Notion becomes the backend (the "body"), and WordPress becomes the frontend (the "head"). A sync tool connects the two.&lt;/p&gt;

&lt;p&gt;Here's how the Notion CMS pipeline works with &lt;a href="https://notipo.com" rel="noopener noreferrer"&gt;Notipo&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Set up your Notion database&lt;/strong&gt; — create a database with properties for Title, Slug, Category, Tags, SEO Keyword, Meta Description, and Status.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connect Notion to Notipo&lt;/strong&gt; — authorize your Notion workspace via OAuth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connect WordPress&lt;/strong&gt; — add your WordPress site URL and an application password.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write in Notion&lt;/strong&gt; — headings, images, code blocks, callouts, and tables all sync to WordPress as Gutenberg blocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish&lt;/strong&gt; — change the Status property to Publish. Notipo syncs everything including images to the WordPress media library, SEO metadata to your plugin, and generates a featured image.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The sync is one-way: Notion to WordPress. Notion is always the source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  SEO With a Notion CMS
&lt;/h2&gt;

&lt;p&gt;Notipo auto-detects and writes metadata to four SEO plugins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rank Math&lt;/strong&gt; — focus keyword, SEO title, meta description&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Yoast SEO&lt;/strong&gt; — focus keyword, SEO title, meta description&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEOPress&lt;/strong&gt; — target keyword, title, description&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All in One SEO&lt;/strong&gt; — focus keyword, title, description&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No configuration needed. Install any of these plugins on your WordPress site, add SEO Keyword and Meta Description properties to your Notion database, and Notipo handles the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Image Problem (and the Fix)
&lt;/h2&gt;

&lt;p&gt;Every Notion CMS setup has to solve the image problem. Notion stores images on Amazon S3 with temporary signed URLs that expire after approximately one hour.&lt;/p&gt;

&lt;p&gt;Notipo solves this by downloading each image during sync and uploading it to your WordPress media library. The WordPress URL is permanent. On subsequent syncs, it checks which images are already cached and only uploads new or changed ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Notion&lt;/strong&gt; — free plan covers unlimited pages and databases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WordPress hosting&lt;/strong&gt; — starts around $3/month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notipo Free&lt;/strong&gt; — 5 posts per month, full image caching, SEO metadata sync&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notipo Pro&lt;/strong&gt; — $19/month for unlimited posts, AI-generated featured images, and priority sync. 7-day free trial, no credit card required.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can Notion be used as a CMS?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Notion works well as a content management backend — databases, properties, status workflows, and rich editing. But it lacks publishing features like custom domains, SEO metadata, sitemaps, and permanent image URLs. To use Notion as a full CMS, pair it with WordPress and a sync tool like Notipo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Notion better than WordPress as a CMS?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Notion is a better writing and content organization tool. WordPress is a better publishing platform. The ideal setup uses both: Notion for drafting, WordPress for SEO and serving content. Notipo connects the two automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does Notion have an API for CMS use?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. The Notion API lets you read pages and database entries programmatically. Sync tools like Notipo use the API to pull content and publish it to WordPress — including images, formatting, and metadata.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are the limitations of using Notion as a CMS?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Notion images use temporary S3 URLs that expire after about one hour. There's no built-in SEO control, no custom domains for published pages, no sitemap generation, and no analytics.&lt;/p&gt;

</description>
      <category>notion</category>
      <category>wordpress</category>
      <category>cms</category>
      <category>blogging</category>
    </item>
    <item>
      <title>Notion WordPress Integration: Connect Notion to WordPress in 2026</title>
      <dc:creator>Kjetil Furås</dc:creator>
      <pubDate>Sat, 18 Apr 2026 08:28:56 +0000</pubDate>
      <link>https://dev.to/kfuras/notion-wordpress-integration-connect-notion-to-wordpress-in-2026-3i64</link>
      <guid>https://dev.to/kfuras/notion-wordpress-integration-connect-notion-to-wordpress-in-2026-3i64</guid>
      <description>&lt;p&gt;Notion is where you write. WordPress is where you publish. The problem: there's no official Notion WordPress integration. Connecting them requires a third-party tool, and the tool you pick determines whether images break, SEO metadata transfers, and formatting survives the move.&lt;/p&gt;

&lt;p&gt;This guide covers every way to integrate Notion with WordPress — what each approach handles, what it doesn't, and how to set up a sync that actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Notion and WordPress Need an Integration
&lt;/h2&gt;

&lt;p&gt;Notion is a better writing environment than the WordPress editor. Drag-and-drop blocks, inline databases, slash commands, nested pages — writers are faster in Notion. But Notion has no SEO, no custom domain, no analytics, and Google rarely indexes Notion pages.&lt;/p&gt;

&lt;p&gt;WordPress handles all of that. What it lacks is a good writing experience. The ideal setup: write in Notion, publish to WordPress, and never copy-paste between them.&lt;/p&gt;

&lt;p&gt;A Notion WordPress integration bridges this gap. It reads your Notion content, converts it to WordPress-compatible format (Gutenberg blocks), handles images, maps SEO metadata, and publishes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Good Integration Must Handle
&lt;/h2&gt;

&lt;p&gt;Not all Notion-to-WordPress integrations are equal. The critical features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Image handling&lt;/strong&gt; — Notion stores images on S3 with temporary URLs that expire in about an hour. The integration must download images and upload them to your WordPress media library.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO metadata&lt;/strong&gt; — focus keyword, meta description, and SEO title should transfer to your SEO plugin (Rank Math, Yoast, SEOPress, or All in One SEO)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Featured images&lt;/strong&gt; — either mapped from a Notion property or auto-generated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code blocks&lt;/strong&gt; — language metadata must survive so syntax highlighting works&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gutenberg blocks&lt;/strong&gt; — content should convert to proper WordPress blocks, not raw HTML&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Categories and tags&lt;/strong&gt; — mapped from Notion database properties&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-sync&lt;/strong&gt; — updating a post in Notion should update the same WordPress post, not create a duplicate&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 1: Notipo (API-Based Sync)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://notipo.com" rel="noopener noreferrer"&gt;Notipo&lt;/a&gt; is a dedicated Notion-to-WordPress sync service. It connects to both platforms via their APIs and handles the full content pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Connect your Notion workspace via OAuth&lt;/li&gt;
&lt;li&gt;Connect WordPress with your site URL and an application password&lt;/li&gt;
&lt;li&gt;Duplicate the Notipo template database into your workspace&lt;/li&gt;
&lt;li&gt;Write a post in Notion, fill in properties (category, tags, SEO keyword, meta description)&lt;/li&gt;
&lt;li&gt;Set the status to "Publish" — Notipo syncs automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What Notipo Handles
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Images downloaded and uploaded to WordPress media library (permanent URLs)&lt;/li&gt;
&lt;li&gt;SEO metadata written to Rank Math, Yoast, SEOPress, or All in One SEO&lt;/li&gt;
&lt;li&gt;Featured images auto-generated (standard or AI-powered on Pro)&lt;/li&gt;
&lt;li&gt;Code blocks with language metadata preserved&lt;/li&gt;
&lt;li&gt;Gutenberg block conversion for headings, lists, quotes, callouts, toggles&lt;/li&gt;
&lt;li&gt;Categories and tags mapped from Notion database properties&lt;/li&gt;
&lt;li&gt;Re-syncs update the existing WordPress post — no duplicates&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  API and CLI Access
&lt;/h3&gt;

&lt;p&gt;Notipo Pro includes a REST API and CLI for programmatic publishing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Publish a post via the Notipo CLI&lt;/span&gt;
npx notipo publish &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"My Blog Post"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Markdown content here..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--category&lt;/span&gt; &lt;span class="s2"&gt;"Guides"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--seo-keyword&lt;/span&gt; &lt;span class="s2"&gt;"target keyword"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--slug&lt;/span&gt; &lt;span class="s2"&gt;"my-blog-post"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pricing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free&lt;/strong&gt; — 5 posts/month, full image handling, SEO metadata, code highlighting, standard featured images&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro ($19/month)&lt;/strong&gt; — unlimited posts, AI featured images, instant sync, REST API and CLI access. 7-day free trial, no credit card required.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 2: WP Sync for Notion (WordPress Plugin)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/plugins/wp-sync-for-notion/" rel="noopener noreferrer"&gt;WP Sync for Notion&lt;/a&gt; is a WordPress plugin that pulls content from Notion. You install it on your WordPress site, connect your Notion API key, and link individual pages or databases.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free&lt;/strong&gt; — sync individual Notion pages, basic content conversion, image downloading, manual sync trigger&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro (~$9/month)&lt;/strong&gt; — full database sync, scheduled/webhook sync, SEO plugin integration, property mapping&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 3: Zapier or Make (Automation Platforms)
&lt;/h2&gt;

&lt;p&gt;Zapier and Make can create a basic Notion WordPress integration. The trigger watches a Notion database, and the action creates a WordPress post via the REST API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What breaks:&lt;/strong&gt; Images (Notion S3 URLs expire within an hour), formatting (raw block data needs Gutenberg conversion), SEO metadata (not handled natively), featured images (not handled), re-syncs (need extra logic).&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 4: Custom Integration via Notion API
&lt;/h2&gt;

&lt;p&gt;Build a custom integration using the &lt;a href="https://developers.notion.com/" rel="noopener noreferrer"&gt;Notion API&lt;/a&gt; and the &lt;a href="https://developer.wordpress.org/rest-api/" rel="noopener noreferrer"&gt;WordPress REST API&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudocode: custom Notion → WordPress sync&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notionPage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page_id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;notion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;block_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page_id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Convert Notion blocks to Gutenberg HTML&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gutenbergHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;convertBlocks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blocks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Download images from Notion S3 URLs before they expire&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;processedHtml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;downloadAndReplaceImages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gutenbergHtml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wpClient&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create or update WordPress post&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;notionPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;plain_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;processedHtml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;publish&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Building this from scratch means handling every edge case yourself: nested blocks, image expiry, Gutenberg block syntax, SEO plugin meta keys, featured image generation, and update-vs-create logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Important: All Integrations Are One-Way
&lt;/h2&gt;

&lt;p&gt;Every Notion WordPress integration available today syncs in one direction: Notion to WordPress. You write and edit in Notion, and changes flow to WordPress. Notion is the single source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does Notion have a built-in WordPress integration?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. You need a third-party tool — Notipo, WP Sync for Notion, or an automation platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the best Notion to WordPress integration?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Notipo is the most complete option — images, SEO for 4 plugins, featured images, code highlighting, and Gutenberg blocks. WP Sync is simpler but needs Pro for SEO and database sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I sync Notion to WordPress automatically?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Notipo watches a Notion database and syncs on status change. WP Sync Pro supports scheduled sync.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://notipo.com/blog/notion-wordpress-integration" rel="noopener noreferrer"&gt;notipo.com/blog/notion-wordpress-integration&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>notion</category>
      <category>wordpress</category>
      <category>automation</category>
      <category>blogging</category>
    </item>
    <item>
      <title>Build a Custom MCP Server in Python in 2026</title>
      <dc:creator>Kjetil Furås</dc:creator>
      <pubDate>Fri, 17 Apr 2026 09:14:59 +0000</pubDate>
      <link>https://dev.to/kfuras/build-a-custom-mcp-server-in-python-in-2026-1oc4</link>
      <guid>https://dev.to/kfuras/build-a-custom-mcp-server-in-python-in-2026-1oc4</guid>
      <description>&lt;p&gt;Building tools that Claude Code can call directly from your terminal changes how you work with AI. Instead of copy-pasting data into prompts, you give Claude direct access to your systems through MCP servers.&lt;/p&gt;

&lt;p&gt;I've built several MCP servers in Python — for WordPress publishing, Skool community management, and YouTube analytics. Every one follows the same pattern. Here's exactly how to build a custom MCP server in Python from scratch using the official SDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is an MCP Server?
&lt;/h2&gt;

&lt;p&gt;MCP (Model Context Protocol) is an open standard from Anthropic that lets AI assistants call external tools. You define tools with typed inputs and outputs. Claude Code calls them mid-conversation like native functions.&lt;/p&gt;

&lt;p&gt;The protocol runs over JSON-RPC through stdio. Your server starts as a subprocess that Claude Code spawns and talks to via stdin/stdout. No HTTP. No webhooks. Just a Python process that reads and writes JSON.&lt;/p&gt;

&lt;p&gt;If you've built a CLI tool before, building an MCP server is simpler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set Up the Project
&lt;/h2&gt;

&lt;p&gt;Start with a clean directory and install the MCP Python SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-mcp-server &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;my-mcp-server
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install &lt;/span&gt;mcp[cli]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;mcp[cli]&lt;/code&gt; package includes the server framework and a dev inspector for testing. That's the only dependency you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Your First Tool
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;server.py&lt;/code&gt;. Every MCP server follows the same structure: create a server instance, define tools with decorators, and run the stdio transport.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.fastmcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastMCP&lt;/span&gt;

&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_system_info&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return basic system information.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt;

    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OS: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;system&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Python: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;python_version&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Machine: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;count_files&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Count files in a directory, optionally filtered by extension.

    Args:
        directory: Path to the directory to scan
        extension: File extension to filter by (e.g. &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.py&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;)
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;expanduser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_dir&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is not a valid directory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Found &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; files in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; matching &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;extension&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stdio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things to notice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;@mcp.tool()&lt;/code&gt; decorator registers a function as a callable tool. The function name becomes the tool name.&lt;/li&gt;
&lt;li&gt;Type hints on parameters are required. MCP uses them to generate the JSON schema that Claude reads.&lt;/li&gt;
&lt;li&gt;The docstring becomes the tool description. Claude uses this to decide when to call it. Write it like you're explaining to a colleague.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Add a Tool With External API Access
&lt;/h2&gt;

&lt;p&gt;Most useful MCP servers talk to external services. Here's a tool that checks the status of a website:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;


&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_website&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Check if a website is reachable and return its status code and response time.

    Args:
        url: Full URL to check (e.g. https://example.com)
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;AsyncClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Status: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Response time: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;total_seconds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;content-type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;unknown&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error reaching &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Async tools work out of the box. The MCP SDK handles the event loop. Use &lt;code&gt;httpx&lt;/code&gt; for HTTP calls — it's async-native and comes with timeout handling.&lt;/p&gt;

&lt;p&gt;Install it alongside the SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;httpx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test Before Connecting to Claude
&lt;/h2&gt;

&lt;p&gt;The MCP CLI includes a dev inspector that lets you test tools without Claude:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mcp dev server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens a web UI where you can call each tool, inspect the JSON schema, and verify outputs. Fix issues here before connecting to Claude Code — debugging inside a conversation is slower.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect to Claude Code
&lt;/h2&gt;

&lt;p&gt;Add your server to Claude Code's MCP configuration. Open &lt;code&gt;.claude/settings.json&lt;/code&gt; in your project root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"my-tools"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/path/to/my-mcp-server/.venv/bin/python"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"server.py"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/path/to/my-mcp-server"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the full path to the Python binary inside your venv. Claude Code spawns this as a subprocess. If you use a relative path or the system Python, it won't find your installed packages.&lt;/p&gt;

&lt;p&gt;Restart Claude Code. Your tools appear as &lt;code&gt;mcp__my-tools__get_system_info&lt;/code&gt;, &lt;code&gt;mcp__my-tools__count_files&lt;/code&gt;, and &lt;code&gt;mcp__my-tools__check_website&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add Resources for Context
&lt;/h2&gt;

&lt;p&gt;Tools handle actions. Resources handle data that Claude reads as context. Use them for configuration, reference data, or live state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@mcp.resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;config://settings&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_settings&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Current server configuration.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

    &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools_registered&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_timeout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude can read this resource to understand your server's capabilities before calling tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error Handling That Works
&lt;/h2&gt;

&lt;p&gt;MCP tools should never raise unhandled exceptions. A crash kills the server process and disconnects all tools mid-conversation. Wrap external calls and return error strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Read a configuration file and return its contents.

    Args:
        path: Path to the config file
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;config_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;expanduser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;config_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; does not exist&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;config_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;st_size&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100_000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is too large (&amp;gt;100KB)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;config_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;PermissionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error: no permission to read &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error reading &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Return errors as strings, not exceptions. Claude reads the error message and adjusts — retries with a different path, asks the user for help, or moves on. An unhandled exception just kills the connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Structure for Production
&lt;/h2&gt;

&lt;p&gt;Once your server grows past 3-4 tools, split into modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-mcp-server/
  server.py          # Entry point — imports and registers tools
  tools/
    system.py        # System info tools
    web.py           # HTTP/API tools
    files.py         # File operation tools
  requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep &lt;code&gt;server.py&lt;/code&gt; minimal. Import tools from modules so each file stays focused:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp.server.fastmcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastMCP&lt;/span&gt;

&lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-tools&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Import tool modules — decorators register on import
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tools.system&lt;/span&gt;  &lt;span class="c1"&gt;# noqa: F401
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tools.web&lt;/span&gt;     &lt;span class="c1"&gt;# noqa: F401
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;tools.files&lt;/span&gt;   &lt;span class="c1"&gt;# noqa: F401
&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stdio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each tool module imports the shared &lt;code&gt;mcp&lt;/code&gt; instance and uses &lt;code&gt;@mcp.tool()&lt;/code&gt; as usual. This pattern scales to 20+ tools without the main file becoming unreadable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Printing to stdout.&lt;/strong&gt; MCP uses stdout for JSON-RPC. Any &lt;code&gt;print()&lt;/code&gt; statement corrupts the protocol and crashes the connection. Use &lt;code&gt;logging&lt;/code&gt; to stderr instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Missing type hints.&lt;/strong&gt; Every parameter needs a type annotation. Without one, the MCP SDK can't generate the JSON schema, and Claude won't know what arguments to pass.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blocking the event loop.&lt;/strong&gt; If you have a CPU-heavy tool, run it in an executor. The MCP server is single-threaded — a blocking call freezes all tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;


&lt;span class="nd"&gt;@mcp.tool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;heavy_computation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Run a CPU-intensive task without blocking other tools.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;loop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_event_loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_in_executor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;process_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need to restart Claude Code after changing my MCP server?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Claude Code loads the MCP configuration at startup and spawns servers as subprocesses. After editing &lt;code&gt;server.py&lt;/code&gt;, restart Claude Code to pick up changes. The &lt;code&gt;mcp dev&lt;/code&gt; inspector is faster for iterating.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can one MCP server connect to multiple AI clients?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. The MCP protocol is client-agnostic. The same server works with Claude Code, Claude Desktop, and any other MCP-compatible client. No code changes needed — just point each client to your server's entry point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the difference between tools and resources?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tools perform actions — they take inputs, do something, and return results. Resources provide read-only context — configuration, reference data, live state. Claude reads resources for background knowledge and calls tools to take action.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there a limit on how many tools one server can expose?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No hard limit in the protocol. I run servers with 10-15 tools without issues. If you go past 20, consider splitting into multiple servers by domain (one for database ops, one for API calls, etc.) to keep tool descriptions focused.&lt;/p&gt;




&lt;p&gt;I'm documenting the full build process — MCP servers, Claude Code agent patterns, and autonomous workflows — in my &lt;a href="https://go.kjetilfuras.com/blog" rel="noopener noreferrer"&gt;Build &amp;amp; Automate&lt;/a&gt; community. If you want step-by-step modules with real production code, that's where it's happening.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was published using &lt;a href="https://notipo.com" rel="noopener noreferrer"&gt;Notipo&lt;/a&gt; — my Notion-to-WordPress sync tool. Write in Notion, publish to WordPress automatically.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>mcp</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>Sync Notion to WordPress: Keep Your Content in Sync Automatically</title>
      <dc:creator>Kjetil Furås</dc:creator>
      <pubDate>Thu, 16 Apr 2026 12:38:25 +0000</pubDate>
      <link>https://dev.to/kfuras/sync-notion-to-wordpress-keep-your-content-in-sync-automatically-2mmm</link>
      <guid>https://dev.to/kfuras/sync-notion-to-wordpress-keep-your-content-in-sync-automatically-2mmm</guid>
      <description>&lt;p&gt;You write in Notion. Your blog runs on WordPress. The gap between them is where you lose 15-30 minutes per post — copy-pasting content, re-uploading images, setting SEO fields, fixing formatting that breaks in Gutenberg.&lt;/p&gt;

&lt;p&gt;There are several ways to publish from Notion to WordPress automatically. This guide covers the main options, when each one makes sense, and how to set up the fastest workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why publish from Notion to WordPress?
&lt;/h2&gt;

&lt;p&gt;Notion is a better writing environment than the WordPress editor. Markdown-style formatting, nested pages, databases with properties, inline images — it's where most writers already work.&lt;/p&gt;

&lt;p&gt;WordPress is a better publishing platform. Custom domains, SEO plugins, themes, full control over your site. Over 40% of the web runs on WordPress.&lt;/p&gt;

&lt;p&gt;The problem: getting content from one to the other is tedious. Manual copy-paste breaks formatting. Images need re-uploading. Categories, tags, featured images, and meta descriptions need setting manually. A 1,000-word post takes 5 minutes to write and 15 minutes to publish.&lt;/p&gt;

&lt;p&gt;The solution: automate the sync. Write in Notion, publish to WordPress with one action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: Notipo — API-first Notion to WordPress sync
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://notipo.com" rel="noopener noreferrer"&gt;Notipo&lt;/a&gt; connects your Notion database to your WordPress site. When you change the status of a page in Notion to "Publish," it syncs the content to WordPress automatically — including images, SEO metadata, categories, tags, and featured images.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Sign up at &lt;a href="https://notipo.com" rel="noopener noreferrer"&gt;notipo.com&lt;/a&gt; (free plan available)&lt;/li&gt;
&lt;li&gt;Connect your Notion workspace (one-click OAuth)&lt;/li&gt;
&lt;li&gt;Connect your WordPress site (enter URL, click approve in WP admin)&lt;/li&gt;
&lt;li&gt;Write a post in Notion, set the status to "Publish"&lt;/li&gt;
&lt;li&gt;Notipo converts the Notion content to WordPress blocks and publishes it&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What syncs automatically
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Content&lt;/strong&gt; — headings, paragraphs, lists, code blocks, quotes, callouts, toggle blocks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Images&lt;/strong&gt; — uploaded to your WordPress media library automatically&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO metadata&lt;/strong&gt; — focus keyword and meta description from Notion properties&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Categories and tags&lt;/strong&gt; — mapped from your Notion database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Featured images&lt;/strong&gt; — AI-generated from Unsplash or Google Gemini on the Pro plan&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code highlighting&lt;/strong&gt; — syntax highlighting preserved with language detection&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  API and CLI access
&lt;/h3&gt;

&lt;p&gt;Notipo also has a REST API and CLI tool for automation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;notipo posts create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"My Post Title"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;post.md&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--category&lt;/span&gt; &lt;span class="s2"&gt;"Tutorial"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--seo-keyword&lt;/span&gt; &lt;span class="s2"&gt;"target keyword"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--publish&lt;/span&gt; &lt;span class="nt"&gt;--wait&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how AI coding agents like Claude Code publish blog posts — one command, full SEO, no manual work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pricing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free&lt;/strong&gt; — 5 posts/month, one-click WordPress setup, SEO metadata, code highlighting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pro ($19/month)&lt;/strong&gt; — Unlimited posts, AI featured images, instant sync, priority support, 7-day free trial&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 2: WP Sync for Notion (WordPress plugin)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/plugins/wp-sync-for-notion/" rel="noopener noreferrer"&gt;WP Sync for Notion&lt;/a&gt; is a WordPress plugin by WPConnect. Install it, connect your Notion database, and it pulls content into WordPress posts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requires a WordPress plugin installation&lt;/li&gt;
&lt;li&gt;Free version only syncs individual pages, not databases&lt;/li&gt;
&lt;li&gt;Pro version needed for database sync, custom post types, and SEO plugin integration&lt;/li&gt;
&lt;li&gt;Image handling can be inconsistent (Notion image URLs expire)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 3: Zapier or Make
&lt;/h2&gt;

&lt;p&gt;Connect Notion and WordPress through Zapier or Make. When a Notion database item changes status, a Zap or scenario creates a WordPress post via the REST API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Requires paid plan for multi-step workflows&lt;/li&gt;
&lt;li&gt;Content formatting is basic — you lose rich blocks like callouts, toggles, and code highlighting&lt;/li&gt;
&lt;li&gt;Images require extra steps to transfer (Notion image URLs expire after 1 hour)&lt;/li&gt;
&lt;li&gt;Ongoing maintenance when APIs change&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Option 4: Cloudpress
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.usecloudpress.com/" rel="noopener noreferrer"&gt;Cloudpress&lt;/a&gt; exports content from Notion to WordPress, Webflow, and other CMS platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Export-based, not sync-based — changes in Notion require a new export&lt;/li&gt;
&lt;li&gt;Priced per export ($0.50-$1 per article on lower plans)&lt;/li&gt;
&lt;li&gt;No automatic publishing on status change&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Which option should you pick?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Choose Notipo if&lt;/strong&gt; you want automatic one-way sync (Notion to WordPress) triggered by a Notion status change. Best for bloggers and content teams who publish regularly and want zero manual steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose WP Sync for Notion if&lt;/strong&gt; you prefer a WordPress plugin approach and want everything managed from your WordPress dashboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Zapier/Make if&lt;/strong&gt; you need a custom multi-step workflow that goes beyond Notion-to-WordPress.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Choose Cloudpress if&lt;/strong&gt; you export occasionally rather than publishing regularly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common issues when syncing Notion to WordPress
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Images break after sync.&lt;/strong&gt; Notion serves images through temporary signed URLs that expire after 1 hour. Any sync tool must download and re-upload images to your WordPress media library.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Formatting doesn't match.&lt;/strong&gt; Notion blocks don't map 1:1 to WordPress Gutenberg blocks. Callouts, toggle lists, and synced blocks need special handling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SEO metadata gets lost.&lt;/strong&gt; Unless your tool explicitly maps Notion properties to your SEO plugin, you'll need to set meta descriptions and focus keywords manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Can I use Notion as a full CMS for WordPress?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. With the right sync tool, Notion becomes your content editor and WordPress becomes your publishing platform. You never need to open the WordPress editor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need a self-hosted WordPress site?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For most sync tools, yes. Self-hosted WordPress (wordpress.org) supports the REST API and application passwords needed for automated publishing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will my existing WordPress theme work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Notion-to-WordPress sync tools create standard WordPress posts with Gutenberg blocks. Your theme displays them like any other post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens if I edit the post in Notion after publishing?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With sync-based tools like Notipo, changes in Notion automatically update the WordPress post. With export-based tools like Cloudpress, you need to re-export manually.&lt;/p&gt;

</description>
      <category>notion</category>
      <category>wordpress</category>
      <category>automation</category>
      <category>blogging</category>
    </item>
    <item>
      <title>Build a Sales Follow-Up Agent With the Claude Agent SDK</title>
      <dc:creator>Kjetil Furås</dc:creator>
      <pubDate>Tue, 14 Apr 2026 09:42:54 +0000</pubDate>
      <link>https://dev.to/kfuras/build-a-sales-follow-up-agent-with-the-claude-agent-sdk-37hh</link>
      <guid>https://dev.to/kfuras/build-a-sales-follow-up-agent-with-the-claude-agent-sdk-37hh</guid>
      <description>&lt;p&gt;A Norwegian sales manager just told VG his team closed 1,000+ deals in three months using AI agents. He called it "going from cycling to flying a jet." He didn't say which tools. So I'll show you how to build one yourself with the Claude Agent SDK — the same SDK I use to run Koda, my autonomous marketing agent.&lt;/p&gt;

&lt;p&gt;This is a working claude agent sdk tutorial you can fork today. No theory. No "imagine if." Real code, real tools, real scheduling.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the agent actually does
&lt;/h2&gt;

&lt;p&gt;A sales follow-up agent earns its keep by doing three boring things reliably:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads new leads from a CRM or spreadsheet&lt;/li&gt;
&lt;li&gt;Decides which ones need a follow-up today based on rules you define&lt;/li&gt;
&lt;li&gt;Drafts a personalized message and either sends it or queues it for approval&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. No magic. The boring version is what actually moves revenue.&lt;/p&gt;

&lt;p&gt;Here's the shape of the agent loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;claude_agent_sdk&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ClaudeAgentOptions&lt;/span&gt;

&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ClaudeAgentOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;allowed_tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Read&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;mcp_servers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;airtable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;npx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;args&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;airtable-mcp-server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AIRTABLE_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AIRTABLE_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gmail&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;args&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gmail_mcp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Run the daily sales follow-up cycle. Read the skill file first.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole runtime. The intelligence lives in two places: the system prompt and a skill file. The SDK handles tool calls, retries, and the model loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The system prompt is where the personality lives
&lt;/h2&gt;

&lt;p&gt;Your agent needs to know who it is, what it's allowed to do, and what counts as done. I keep this short and specific.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;You are a sales follow-up agent for Acme Co.

Your job: read leads from Airtable, identify who needs a follow-up today,
draft a message in my voice, and queue it as a Gmail draft for my review.

Rules:
&lt;span class="p"&gt;-&lt;/span&gt; Never send emails directly. Always create drafts.
&lt;span class="p"&gt;-&lt;/span&gt; Never follow up a lead more than once in 7 days.
&lt;span class="p"&gt;-&lt;/span&gt; If a lead replied, mark them as active and stop the sequence.
&lt;span class="p"&gt;-&lt;/span&gt; Log every action to ~/agent/logs/YYYY-MM-DD.md

Voice: direct, concise, first person. No "I hope this finds you well."
No hype words. One specific reason for the outreach. One clear ask.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what's missing: no chain-of-thought prompts, no "think step by step," no roleplay. The SDK model is already good at reasoning. What it needs is boundaries and memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The skill file is the recipe
&lt;/h2&gt;

&lt;p&gt;The system prompt says what the agent is. The skill file says how to do today's task. I keep mine in &lt;code&gt;~/agent/skills/daily-followup.md&lt;/code&gt; and reference it from the prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Daily Follow-Up Skill&lt;/span&gt;

&lt;span class="gu"&gt;## Steps&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; Fetch all Airtable records where &lt;span class="sb"&gt;`status = "lead"`&lt;/span&gt; and &lt;span class="sb"&gt;`last_contact &amp;lt; 7 days ago = false`&lt;/span&gt;.
&lt;span class="p"&gt;2.&lt;/span&gt; For each lead, check Gmail for any reply from that email address.
&lt;span class="p"&gt;3.&lt;/span&gt; If replied, update Airtable status to "active" and skip.
&lt;span class="p"&gt;4.&lt;/span&gt; If not replied, draft a follow-up using the lead's company context.
&lt;span class="p"&gt;5.&lt;/span&gt; Create a Gmail draft — do NOT send.
&lt;span class="p"&gt;6.&lt;/span&gt; Log the action.

&lt;span class="gu"&gt;## Draft template&lt;/span&gt;

Subject: Re: {{previous_subject}}

Hi {{first_name}},

Following up on {{specific_thing_from_last_message}}.
{{one_sentence_with_new_angle_or_value}}.

Worth a quick call this week?

— {{sender_name}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Skills are just markdown. No YAML, no DSL. The agent reads them as context and follows them. I keep a dozen of these for Koda — each covers a single recurring task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wire in real tools with MCP
&lt;/h2&gt;

&lt;p&gt;The agent is useless without data. The Claude Agent SDK speaks MCP (Model Context Protocol), which means any MCP server becomes a tool the agent can call. For sales follow-up, I wire in three:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"airtable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"airtable-mcp-server"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gmail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"python3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gmail_mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"slack"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@modelcontextprotocol/server-slack"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No custom glue code. No webhooks. The agent reads Airtable, reads Gmail, and posts to Slack when it needs approval — all through MCP. This is the piece most tutorials skip, and it's the piece that makes the agent useful in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schedule it so it runs without you
&lt;/h2&gt;

&lt;p&gt;An agent that only runs when you type a prompt is a chatbot. An agent that runs on a schedule is an employee. I use a simple JSON task file and a daemon that wakes the SDK at the scheduled time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sales_followup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Run the daily sales follow-up cycle. Read ~/agent/skills/daily-followup.md first."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"cron"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0 9 * * 1-5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"approval"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;type: "approval"&lt;/code&gt; means the agent drafts and waits — it posts the drafts to my Slack and I either approve or reject each one. &lt;code&gt;type: "silent"&lt;/code&gt; would ship them automatically. For sales outreach I always use approval until I trust the quality for a specific customer segment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost and throughput
&lt;/h2&gt;

&lt;p&gt;One daily run on 50 leads uses roughly 30-80k tokens with Sonnet. That's under $1 per day per agent. Flaaten's 1,000 conversions in three months suggests the team runs at least one outreach agent plus a qualification agent and a scheduler. Total SDK cost for that operation: maybe $100/month. The bottleneck is never the model. It's the quality of your lead list.&lt;/p&gt;

&lt;h2&gt;
  
  
  What breaks
&lt;/h2&gt;

&lt;p&gt;Three things break in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stale skill files.&lt;/strong&gt; The agent follows the skill exactly. If the skill says "check Airtable" but you moved to HubSpot, it will quietly do nothing. Update the skill, not the prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool permission drift.&lt;/strong&gt; The SDK's &lt;code&gt;allowed_tools&lt;/code&gt; list is a hard boundary. If you add a new MCP server and forget to allow it, the agent reports "I don't have a tool for that" and gives up. Allow explicitly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approval queue neglect.&lt;/strong&gt; An approval-mode agent drafts faster than you review. If you don't review daily, the queue becomes noise and you lose the one message that actually mattered. Either review daily or switch segments to silent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this pattern beats a workflow tool
&lt;/h2&gt;

&lt;p&gt;No canvas. No boxes and arrows. No "here's how we connect step 3 to step 7." The Claude Agent SDK is a function call with a system prompt and a toolbox. The intelligence is in the model, the rules are in a markdown file, and the data comes from MCP servers. That's the whole stack.&lt;/p&gt;

&lt;p&gt;I run five of these agents daily for my own work. One for sales outreach, one for content drafting, one for analytics, one for social scheduling, one for community engagement. Total code across all of them: under 500 lines.&lt;/p&gt;

&lt;p&gt;I'm documenting the full build process — skills, scheduling, approval queues, and the production patterns that keep them stable — in my &lt;a href="https://go.kjetilfuras.com/blog" rel="noopener noreferrer"&gt;Build &amp;amp; Automate&lt;/a&gt; community. If you want step-by-step modules with real production code, that's where it's happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need a Claude Max subscription to use the Agent SDK?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. The SDK works with an API key. You pay per token. For most agents running a few times a day, expect $10-50/month at Sonnet prices. Max is cheaper only if you're also using Claude Code interactively for hours a day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use this with a non-Claude model?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Claude Agent SDK is Claude-specific, but the pattern — system prompt, skill files, MCP tools, scheduled runs — works with any agent framework. I prefer the Claude SDK because the tool-use is more reliable and MCP is natively supported.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How is this different from n8n or Make?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Workflow tools are explicit: every step, every branch, every condition is a box you draw. Agents are declarative: you describe the goal and constraints, and the model decides the path. For rigid processes, workflows win. For anything that requires judgment — "is this lead worth following up today?" — agents win.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it safe to let an agent send emails?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not without approval, not at first. I run every outbound agent in &lt;code&gt;approval&lt;/code&gt; mode for at least two weeks per customer segment. Only after the draft quality is boring and consistent do I flip to silent. Shortcuts here cost you the sender reputation.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was published using &lt;a href="https://notipo.com" rel="noopener noreferrer"&gt;Notipo&lt;/a&gt; — my Notion-to-WordPress sync tool. Write in Notion, publish to WordPress automatically.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>automation</category>
      <category>agents</category>
    </item>
    <item>
      <title>Using Notion as a WordPress CMS: The Complete Guide</title>
      <dc:creator>Kjetil Furås</dc:creator>
      <pubDate>Mon, 13 Apr 2026 12:08:24 +0000</pubDate>
      <link>https://dev.to/kfuras/using-notion-as-a-wordpress-cms-the-complete-guide-5hme</link>
      <guid>https://dev.to/kfuras/using-notion-as-a-wordpress-cms-the-complete-guide-5hme</guid>
      <description>&lt;p&gt;WordPress has a powerful editor, but many content teams prefer writing in Notion. The collaboration features, database views, and familiar interface make it a natural fit for managing blog content. The challenge is bridging the gap between where you write and where you publish.&lt;/p&gt;

&lt;p&gt;This guide shows you how to use Notion as a full content management system for WordPress — from setting up your editorial database to automating the publish pipeline with &lt;a href="https://notipo.com" rel="noopener noreferrer"&gt;Notipo&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Notion Instead of the WordPress Editor?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-time collaboration&lt;/strong&gt; — Multiple people can edit the same page simultaneously. WordPress doesn't support this natively.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database views&lt;/strong&gt; — See all posts in a board (Kanban), table, calendar, or gallery view. Filter by status, category, author, or any custom property.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Templates&lt;/strong&gt; — Create reusable page templates so every post starts with the same structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comments and mentions&lt;/strong&gt; — Leave inline comments, mention team members, discuss edits without leaving the document.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Offline editing&lt;/strong&gt; — Notion works offline and syncs when you reconnect.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting Up Your Editorial Database
&lt;/h2&gt;

&lt;p&gt;The foundation of your Notion CMS is a database where each row represents a blog post. Duplicate the &lt;a href="https://free-dentist-6b2.notion.site/30d842af972f8091a104eb8773fbf390?v=30d842af972f8091a104eb8773fbf390" rel="noopener noreferrer"&gt;Notipo blog template&lt;/a&gt; which has everything pre-configured.&lt;/p&gt;

&lt;h3&gt;
  
  
  Required Properties
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name&lt;/strong&gt; (Title) — The post title&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status&lt;/strong&gt; (Select) — Controls the pipeline: "Post to Wordpress," "Publish," "Update Wordpress," "Ready to Review," and "Published"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Category&lt;/strong&gt; (Select) — Auto-populated from your WordPress categories&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tags&lt;/strong&gt; (Multi-select) — Auto-populated from your WordPress tags&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slug&lt;/strong&gt; (Text) — The URL slug for WordPress&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Featured Image Title&lt;/strong&gt; (Text) — Text rendered on the auto-generated featured image&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO Keyword&lt;/strong&gt; (Text) — The focus keyword for your SEO plugin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WordPress Link&lt;/strong&gt; (URL) — Auto-filled after syncing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Organizing Content with Database Views
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Board view&lt;/strong&gt; — Columns for each status. Drag cards between columns to trigger actions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Table view&lt;/strong&gt; — See all posts with every property visible. Great for bulk editing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calendar view&lt;/strong&gt; — Visualize your publishing schedule by date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gallery view&lt;/strong&gt; — Preview posts with their featured image titles.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Content Pipeline
&lt;/h2&gt;

&lt;p&gt;The Status property drives your entire publishing workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Draft&lt;/strong&gt; — You're writing. No action taken.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Post to Wordpress&lt;/strong&gt; — Notipo syncs content, images, and metadata to WordPress as a draft. Status auto-changes to "Ready to Review."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ready to Review&lt;/strong&gt; — WordPress draft is ready. Review and preview.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish&lt;/strong&gt; — Notipo publishes the draft live. Status changes to "Published" and the WordPress Link updates.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Need to update a published post? Change the status to "Update Wordpress" and Notipo re-syncs while keeping the post live.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Notion Blocks Convert to WordPress
&lt;/h2&gt;

&lt;p&gt;Notipo converts these Notion blocks to native WordPress Gutenberg blocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Paragraphs, headings (H1-H3), and inline formatting&lt;/li&gt;
&lt;li&gt;Bulleted lists, numbered lists, and to-do lists&lt;/li&gt;
&lt;li&gt;Images (uploaded to your WordPress media library)&lt;/li&gt;
&lt;li&gt;Code blocks (with language identifier for syntax highlighting)&lt;/li&gt;
&lt;li&gt;Quotes, dividers, and table of contents&lt;/li&gt;
&lt;li&gt;Bookmarks and embedded links&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  SEO Optimization from Notion
&lt;/h2&gt;

&lt;p&gt;Fill in the SEO Keyword property and Notipo automatically configures your WordPress SEO plugin — focus keyword, SEO title, and meta description. Supports Rank Math, Yoast SEO, SEOPress, and All in One SEO. Your posts are SEO-ready the moment they sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Using Notion as your WordPress CMS takes less than 5 minutes to set up. &lt;a href="https://notipo.com/auth/register" rel="noopener noreferrer"&gt;Create a free Notipo account&lt;/a&gt;, connect Notion and WordPress, and start publishing.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://notipo.com/blog/notion-as-wordpress-cms" rel="noopener noreferrer"&gt;notipo.com/blog/notion-as-wordpress-cms&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>notion</category>
      <category>wordpress</category>
      <category>cms</category>
      <category>productivity</category>
    </item>
    <item>
      <title>From Python Script to App: How an n8n Workflow Became Notipo</title>
      <dc:creator>Kjetil Furås</dc:creator>
      <pubDate>Mon, 13 Apr 2026 12:07:53 +0000</pubDate>
      <link>https://dev.to/kfuras/from-python-script-to-app-how-an-n8n-workflow-became-notipo-2mk0</link>
      <guid>https://dev.to/kfuras/from-python-script-to-app-how-an-n8n-workflow-became-notipo-2mk0</guid>
      <description>&lt;p&gt;Notipo didn't start as a product. It started as a Python script that saved me 10 minutes every time I published a blog post. Over the course of a year, that script grew into an n8n workflow, then a sprawling multi-workflow automation, and finally into the app you see today. This is the story of that evolution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1: A Python Script for Featured Images
&lt;/h2&gt;

&lt;p&gt;It started with a simple annoyance. Every blog post needed a featured image, and I was creating them manually in Canva. Same layout every time — background image, title text, category label. Twenty minutes of dragging text boxes around for something that should be automatic.&lt;/p&gt;

&lt;p&gt;So I wrote a Python script using Pillow. It read titles and categories from a CSV file and generated 1200×628 PNG images. Run the script, get your images. Done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2: Discovering n8n
&lt;/h2&gt;

&lt;p&gt;I discovered &lt;a href="https://n8n.io" rel="noopener noreferrer"&gt;n8n&lt;/a&gt;, the open-source workflow automation tool. I connected Airtable to n8n and turned my Python script into an API endpoint. Now the flow was: fill out an Airtable form, n8n picks it up, calls the Python API, and stores the generated image in MinIO.&lt;/p&gt;

&lt;p&gt;This felt like magic. But it only solved the featured image problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 3: The Full Notion-to-WordPress Workflow
&lt;/h2&gt;

&lt;p&gt;The manual publishing process: write in Notion, copy content, paste into WordPress editor, fix code blocks, fix formatting, upload images one by one, create featured image, set SEO metadata, hit publish. Fifteen to twenty minutes of tedious work after the writing was done.&lt;/p&gt;

&lt;p&gt;I decided to automate the entire thing in n8n. The workflow grew to handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Markdown conversion&lt;/strong&gt; — pulling from Notion's API, converting blocks to Gutenberg&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code block transformation&lt;/strong&gt; — wrapping in Prismatic blocks with language detection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image caching&lt;/strong&gt; — Notion's image URLs expire after an hour, so every image needed downloading and re-uploading to WordPress&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orphan cleanup&lt;/strong&gt; — removing old images when removed from Notion&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Featured image generation&lt;/strong&gt; — calling the Python API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO metadata&lt;/strong&gt; — setting Rank Math fields via REST API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status management&lt;/strong&gt; — updating Notion after each operation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I ended up with 50+ nodes across multiple paths. Airtable sat at the center with five interconnected tables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where It Broke Down
&lt;/h2&gt;

&lt;p&gt;The automation worked. But living with it revealed problems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The execution log was useless&lt;/strong&gt; — Polling every few minutes created pages of empty "success" runs. Finding the actual sync in the noise was painful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Airtable was an expensive dependency&lt;/strong&gt; — $24/month for a spreadsheet acting as a database. Every API call added latency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The complexity was unmanageable&lt;/strong&gt; — 50+ nodes, multiple paths, debugging meant clicking through node after node. Adding a feature meant touching nodes in multiple places.&lt;/p&gt;

&lt;p&gt;n8n is an incredible tool for connecting APIs and building automations. But there's a complexity threshold where a visual workflow becomes harder to reason about than code. I had crossed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rebuilding as an App
&lt;/h2&gt;

&lt;p&gt;The new stack: Fastify backend with PostgreSQL (replacing Airtable), pg-boss job queue (replacing n8n's execution engine), and Next.js frontend. The Gutenberg block converter became a proper TypeScript module with tests. The image caching pipeline became a service with clear inputs and outputs.&lt;/p&gt;

&lt;p&gt;Every pain point had a direct solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Noisy logs&lt;/strong&gt; → job queue only creates entries for actual work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Airtable costs&lt;/strong&gt; → PostgreSQL with Prisma, zero monthly cost&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging&lt;/strong&gt; → proper error handling, structured logging, stack traces&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature development&lt;/strong&gt; → changing one switch statement, not rewiring workflow paths&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What n8n Taught Me
&lt;/h2&gt;

&lt;p&gt;I don't regret the n8n phase. Building the workflow forced me to understand every edge case of the Notion and WordPress APIs before writing application code. The workflow was a prototype that ran in production. By the time I wrote the app, I had a complete specification from months of real usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons for Anyone Automating with n8n
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;n8n is perfect for prototyping&lt;/strong&gt; — validate an idea in hours instead of days&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch for the complexity cliff&lt;/strong&gt; — if you dread opening the workflow editor, you've passed it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paid dependencies add up&lt;/strong&gt; — evaluate whether a simple database would work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polling is a poor man's webhook&lt;/strong&gt; — look for webhook-based triggers whenever possible&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The workflow is the spec&lt;/strong&gt; — if you outgrow n8n, your workflow is the most detailed specification you could ask for&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;If you're writing in Notion and copy-pasting into WordPress — &lt;a href="https://notipo.com/auth/register" rel="noopener noreferrer"&gt;give Notipo a try&lt;/a&gt;. The free plan covers 5 posts per month. It took me a year of incremental automation to get here. You can set it up in 5 minutes.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://notipo.com/blog/from-n8n-to-app" rel="noopener noreferrer"&gt;notipo.com/blog/from-n8n-to-app&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
      <category>buildinpublic</category>
      <category>saas</category>
    </item>
    <item>
      <title>How to Publish WordPress Posts with OpenClaw (AI Agent Automation)</title>
      <dc:creator>Kjetil Furås</dc:creator>
      <pubDate>Mon, 13 Apr 2026 12:07:13 +0000</pubDate>
      <link>https://dev.to/kfuras/how-to-publish-wordpress-posts-with-openclaw-ai-agent-automation-3n2g</link>
      <guid>https://dev.to/kfuras/how-to-publish-wordpress-posts-with-openclaw-ai-agent-automation-3n2g</guid>
      <description>&lt;p&gt;AI agents are getting good at writing. The missing piece is publishing — getting the generated content from the agent into WordPress with proper formatting, images, and SEO metadata. Most solutions require you to string together Notion APIs, WordPress APIs, image uploads, and SEO plugins yourself. Notipo handles the entire pipeline with a single API call.&lt;/p&gt;

&lt;p&gt;This guide shows how to use &lt;a href="https://github.com/openclaw" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt;, the open-source AI agent framework, to publish WordPress blog posts through Notipo. The same approach works with any AI agent or HTTP client — Claude Code, n8n, Make, custom scripts, or anything that can send a POST request.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use Notipo as Your Publishing Engine
&lt;/h2&gt;

&lt;p&gt;Without Notipo, an AI agent that wants to publish to WordPress needs to handle: Notion API authentication and page creation, markdown-to-Gutenberg block conversion, downloading and re-uploading images, generating a featured image, setting SEO metadata across different plugins, and managing post statuses. That's dozens of API calls and edge cases.&lt;/p&gt;

&lt;p&gt;With Notipo, the agent sends one POST request with a title and body. Notipo creates the Notion page, converts the markdown, uploads images, generates a featured image, sets SEO metadata, and publishes to WordPress. The agent doesn't need Notion or WordPress credentials — just a Notipo API key.&lt;/p&gt;

&lt;h2&gt;
  
  
  The API Endpoint
&lt;/h2&gt;

&lt;p&gt;Everything goes through &lt;code&gt;POST /api/posts/create&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://notipo.com/api/posts/create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: your-api-key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "title": "10 Productivity Hacks for Remote Developers",
    "body": "## Introduction\n\nRemote work is here to stay.",
    "category": "Productivity",
    "tags": ["remote-work", "productivity"],
    "seoKeyword": "productivity hacks remote developers",
    "slug": "productivity-hacks-remote-developers",
    "publish": true
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Notipo Does With That Request
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Creates a Notion page with the title and markdown body converted to Notion blocks&lt;/li&gt;
&lt;li&gt;Inserts inline Unsplash images after specified headings (Pro plan)&lt;/li&gt;
&lt;li&gt;Triggers the sync pipeline — Gutenberg block conversion, image downloads, WordPress media uploads&lt;/li&gt;
&lt;li&gt;Generates a 1200×628 featured image with the category background and title overlay&lt;/li&gt;
&lt;li&gt;Applies SEO metadata — focus keyword, title, description — to Rank Math, Yoast, SEOPress, or AIOSEO&lt;/li&gt;
&lt;li&gt;Creates a WordPress draft or publishes live, depending on the &lt;code&gt;publish&lt;/code&gt; flag&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The agent gets back a &lt;code&gt;jobId&lt;/code&gt; immediately. Processing happens in the background.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Claude Code
&lt;/h2&gt;

&lt;p&gt;Claude Code can publish to WordPress through Notipo using a simple curl command. Since Claude Code has shell access, it can call the Notipo API directly. Set your API key as an environment variable and Claude Code can publish posts as part of any workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using n8n with AI Agents
&lt;/h2&gt;

&lt;p&gt;n8n's AI Agent node can generate content using OpenAI, Claude, or any LLM, then send it to Notipo in one HTTP request. The workflow is four nodes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Trigger&lt;/strong&gt; — schedule, webhook, or manual&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP Request&lt;/strong&gt; — fetch categories and tags from Notipo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Agent&lt;/strong&gt; — generate title, body, SEO keyword, category, tags&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP Request&lt;/strong&gt; — POST /api/posts/create&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Checking Job Status
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://notipo.com/api/jobs?limit&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: your-api-key"&lt;/span&gt;

&lt;span class="c"&gt;# Response includes:&lt;/span&gt;
&lt;span class="c"&gt;# - status: COMPLETED or FAILED&lt;/span&gt;
&lt;span class="c"&gt;# - result.wpUrl: the live WordPress URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For most use cases, fire-and-forget is fine — Notipo sends failure notifications to your configured webhook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://notipo.com/auth/register" rel="noopener noreferrer"&gt;Sign up for Notipo&lt;/a&gt; (free, 7-day Pro trial), connect your Notion database and WordPress site, then grab your API key from Settings → Account. Point your AI agent at &lt;code&gt;POST /api/posts/create&lt;/code&gt; and you're publishing.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://notipo.com/blog/openclaw-wordpress-publishing" rel="noopener noreferrer"&gt;notipo.com/blog/openclaw-wordpress-publishing&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>wordpress</category>
      <category>api</category>
    </item>
  </channel>
</rss>
