Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision 4c9c60bcdb47e6d3cc3b4b710bfb61477bb9f554)
+++ src/routes/posts.js	(revision ca25f360f73b889318b921f39ff70bb54eb6d853)
@@ -354,12 +354,15 @@
   } catch (e) { /* FTS issues non-fatal */ }
 
-  // ActivityPub: federate when a post BECOMES published (draft/scheduled → published
-  // via the editor). A brand-new published post is handled in the create route.
-  if (finalStatus === 'published' && post.status !== 'published' && !fanOnly) {
-    ActivityPubService.deliverCreate(site, {
+  // ActivityPub: federate edits to followers. A post that BECOMES published →
+  // Create (new post); an already-published post that's edited → Update (so
+  // Mastodon refreshes its cached copy). fan_only posts don't federate.
+  if (finalStatus === 'published' && !fanOnly) {
+    const apPost = {
       id: post.id, slug: finalSlug, title: title || finalSlug,
       content: cleanContent, cover_image_url: cover_image_url || null,
       published_at: publishedAt, created_at: post.created_at,
-    }).catch(() => { /* best-effort */ });
+    };
+    if (post.status !== 'published') ActivityPubService.deliverCreate(site, apPost).catch(() => { /* best-effort */ });
+    else ActivityPubService.deliverUpdate(site, apPost).catch(() => { /* best-effort */ });
   }
 
Index: src/services/ActivityPubService.js
===================================================================
--- src/services/ActivityPubService.js	(revision 4c9c60bcdb47e6d3cc3b4b710bfb61477bb9f554)
+++ src/services/ActivityPubService.js	(revision ca25f360f73b889318b921f39ff70bb54eb6d853)
@@ -598,4 +598,25 @@
   };
   for (const inbox of inboxes) deliverWithRetry(site.slug, inbox, del, `${me}#main-key`, keys.private_pem);
+}
+
+// Tell followers an already-published post changed (Update + edited Note) so
+// Mastodon refreshes the cached copy (e.g. after fixing content).
+export async function deliverUpdate(site, post) {
+  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
+  if (!base || !site || !site.slug || !post || !post.id) return;
+  const followers = fStmts().list.all(site.slug);
+  if (!followers.length) return;
+  const inboxes = [...new Set(followers.map((f) => f.shared_inbox || f.inbox).filter(Boolean))];
+  const keys = getOrCreateKeys(site.slug);
+  const me = actorId(base, site.slug);
+  const note = buildNote(base, site, post);
+  note.updated = new Date().toISOString();
+  const update = {
+    '@context': 'https://www.w3.org/ns/activitystreams',
+    id: `${noteId(base, post.id)}#update-${Date.now()}`,
+    type: 'Update', actor: me, to: [PUBLIC], cc: [`${me}/followers`],
+    object: note,
+  };
+  for (const inbox of inboxes) deliverWithRetry(site.slug, inbox, update, `${me}#main-key`, keys.private_pem);
 }
 
@@ -930,5 +951,5 @@
   getOrCreateKeys, apWants, sendAP, actorId, noteId,
   buildActor, buildNote, buildCreate, buildOutbox, buildFollowers,
-  followerCount, deliver, fetchActor, verifyRequest, handleInbox, deliverCreate, deliverDelete,
+  followerCount, deliver, fetchActor, verifyRequest, handleInbox, deliverCreate, deliverDelete, deliverUpdate,
   getInteractions, getInteractionById, buildReplyNote, getOutboxNote, deliverReply, resolveRemoteNote,
   listOutbox, deliverOutboxDelete,
