Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 1b992259058f82476d15f218bcde59509be5bdba)
+++ src/services/ActivityPubService.js	(revision 80c36a11780668405aae6004e333b68963a6b9db)
@@ -240,6 +240,8 @@
     url: human,
     published: new Date(post.published_at || post.created_at || Date.now()).toISOString(),
-    to: [PUBLIC],
-    cc: [`${aId}/followers`],
+    // fan_only = "fans only" → followers-only visibility (delivered to your followers
+    // but not addressed to Public, so Mastodon shows it only to them and can't boost it).
+    to: post.fan_only ? [`${aId}/followers`] : [PUBLIC],
+    cc: post.fan_only ? [] : [`${aId}/followers`],
     tag: Array.isArray(post.tags) ? post.tags.map((t) => ({ type: 'Hashtag', name: '#' + String(t).replace(/\s+/g, '') })) : [],
     replies: `${id}/replies`,
Index: src/services/Scheduler.js
===================================================================
--- src/services/Scheduler.js	(revision 1b992259058f82476d15f218bcde59509be5bdba)
+++ src/services/Scheduler.js	(revision 80c36a11780668405aae6004e333b68963a6b9db)
@@ -32,17 +32,15 @@
       // get a duplicate FTS row → duplicate search hits.
       try { ftsDel.run(p.id); fts.run(HtmlSanitizerService.toPlainText(p.content || ''), p.title || '', p.username || '', p.id); } catch { /* FTS failure is non-fatal */ }
-      // ActivityPub: federate the now-published post to followers.
-      if (!p.fan_only) {
-        try {
-          const site = siteStmt.get(p.site_id);
-          if (site) {
-            ActivityPubService.deliverCreate(site, {
-              id: p.id, slug: p.slug, title: p.title || p.slug,
-              content: p.content, cover_image_url: p.cover_image_url || null,
-              published_at: p.published_at || p.publish_at, created_at: p.created_at,
-            }).catch(() => { /* best-effort */ });
-          }
-        } catch { /* non-fatal */ }
-      }
+      // ActivityPub: federate the now-published post to followers (fan_only → followers-only).
+      try {
+        const site = siteStmt.get(p.site_id);
+        if (site) {
+          ActivityPubService.deliverCreate(site, {
+            id: p.id, slug: p.slug, title: p.title || p.slug,
+            content: p.content, cover_image_url: p.cover_image_url || null,
+            published_at: p.published_at || p.publish_at, created_at: p.created_at, fan_only: p.fan_only,
+          }).catch(() => { /* best-effort */ });
+        }
+      } catch { /* non-fatal */ }
     }
     return due.length;
