<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[LazyRelay Blog]]></title><description><![CDATA[LazyRelay Blog]]></description><link>https://lazyrelay.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 25 Oct 2026 21:07:42 GMT</lastBuildDate><atom:link href="https://lazyrelay.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Why "sent" is not the same as "live"]]></title><description><![CDATA[An API can say OK and the post can still be missing. Here is why LazyRelay checks with the platform that each scheduled post is really live.]]></description><link>https://lazyrelay.hashnode.dev/why-sent-is-not-the-same-as-live</link><guid isPermaLink="true">https://lazyrelay.hashnode.dev/why-sent-is-not-the-same-as-live</guid><category><![CDATA[webdev]]></category><category><![CDATA[api integration]]></category><category><![CDATA[social media growth]]></category><dc:creator><![CDATA[Werner Goss]]></dc:creator><pubDate>Fri, 23 Oct 2026 09:38:28 GMT</pubDate><content:encoded><![CDATA[<p>An API can say OK and the post can still not be visible. A token can expire. A video can fail. The platform answers fast and says nothing about what happens next.</p>
<p>So I built LazyRelay to do one more step. It sends the post. Then it goes back and reads from the platform to see if the post exists and is public.</p>
<p>What the check can tell you: the platform says the post exists and is public. What it cannot tell you: that everyone sees it. I try not to promise more than that.</p>
<p>What is still hard: tokens that expire, media that fails, and platforms that answer slowly. When LazyRelay cannot confirm a post, it flags it in the dashboard so you find out before your audience does.</p>
<p>How do you check that a scheduled post is really visible?</p>
<p>If you want to see how it works: <a href="https://lazyrelay.com/verify-post-published/">https://lazyrelay.com/verify-post-published/</a></p>
]]></content:encoded></item><item><title><![CDATA[Four ways "the post succeeded" can be a lie]]></title><description><![CDATA[I build a tool that schedules social posts. This week I added four new places to publish: WordPress, dev.to, Hashnode and Lemmy. Then I had each adapter…]]></description><link>https://lazyrelay.hashnode.dev/four-ways-the-post-succeeded-can-be-a-lie</link><guid isPermaLink="true">https://lazyrelay.hashnode.dev/four-ways-the-post-succeeded-can-be-a-lie</guid><category><![CDATA[webdev]]></category><category><![CDATA[api]]></category><category><![CDATA[automation]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Werner Goss]]></dc:creator><pubDate>Wed, 30 Sep 2026 16:19:34 GMT</pubDate><content:encoded><![CDATA[<p>I build a tool that schedules social posts. This week I added four new places to publish: WordPress, dev.to, Hashnode and Lemmy. Then I had each adapter reviewed by someone who did not write it. The most useful findings were all the same kind of bug: the code said "published" when it had only proven "the server answered".</p>
<p>Here are four ways that happens.</p>
<h2>1. A 200 is not the post</h2>
<p>My first version of the read-back check fetched the public page and treated HTTP 200 as "live". That passes for a maintenance page, a parked domain, a "coming soon" plugin, or a password-protected post whose public page is just a password form. All of them answer 200.</p>
<p>The fix: read a small part of the page and require the post's title or its slug to appear in it. If the proof cannot name the post, it is not proof.</p>
<h2>2. The URL in the response is not yours to trust</h2>
<p>The read-back used the link the platform sent back. On a blog with a custom domain, that domain is controlled by the customer. It can answer with a redirect to an address inside your own network, and a naive <code>fetch</code> follows it and reports success.</p>
<p>The fix: do not follow redirects blindly. Check every hop, require the final host to match the expected host, and never send credentials past the first host.</p>
<h2>3. A timeout does not mean "it did not happen"</h2>
<p>If the create request times out, the platform may still have created the post. A scheduler that retries now publishes it twice, in public.</p>
<p>The fix: after a network error, look for a post with the same title from the last few minutes before you report a failure, and if you cannot tell, say so instead of retrying blindly.</p>
<h2>4. "Saved as a draft" is not a failure</h2>
<p>Some customers choose to save a draft on the platform instead of publishing. A read-back that insists the post is public will call that a failure and retry it until it gives up. A draft that exists exactly where the customer asked for it is a finished job. It just should not be counted as published.</p>
<p>The fix: give "saved as a draft" its own ending, so it is never retried and never inflates a "verified live" number.</p>
<h2>The pattern</h2>
<p>In all four, the code checked the easy thing (did the server answer?) instead of the thing I actually care about (can a stranger see this post?). Whenever a system reports success, ask what it actually observed, and whether that observation could be true while the post is not live.</p>
<p>I use this idea in LazyRelay, the scheduler I am building: after every post it reads the platform back to confirm the post is really there. You can look at it at <a href="https://lazyrelay.com">https://lazyrelay.com</a>.</p>
<p>What is the strangest "success" you have seen an API report?</p>
]]></content:encoded></item><item><title><![CDATA[Why "the API returned 200" isn't proof your post is live]]></title><description><![CDATA[I build LazyRelay, a social scheduling tool. Something that became obvious once we started looking closely: a successful API response from a social…]]></description><link>https://lazyrelay.hashnode.dev/why-the-api-returned-200-isn-t-proof-your-post-is-live</link><guid isPermaLink="true">https://lazyrelay.hashnode.dev/why-the-api-returned-200-isn-t-proof-your-post-is-live</guid><category><![CDATA[webdev]]></category><category><![CDATA[api]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Werner Goss]]></dc:creator><pubDate>Fri, 28 Aug 2026 15:16:49 GMT</pubDate><content:encoded><![CDATA[<p>I build <a href="https://lazyrelay.com">LazyRelay</a>, a social scheduling tool. Something that became obvious once we started looking closely: a successful API response from a social platform does not mean your content is actually visible to anyone.</p>
<h2>The gap</h2>
<p>Every platform's "create post" endpoint can return <code>200 OK</code> and a post ID, and the content still isn't live. A few real ways that happens:</p>
<ul>
<li>The platform accepts the write, then silently rejects it a few seconds later during async moderation.</li>
<li>Media processing fails after the initial call succeeds, before the post actually renders.</li>
<li>The account hits a rate limit or shadow restriction that doesn't surface as an error on the write itself.</li>
<li>A token has a narrower scope than you think, so the write "succeeds" against a sandbox or test context you didn't intend.</li>
</ul>
<p>If your scheduler treats "the POST call didn't throw" as "it worked," you'll eventually tell a customer their content is live when it isn't — and you won't find out until they do.</p>
<h2>What we do instead</h2>
<p>After every publish attempt, we make a second, independent read-back call — not trusting the write response, actually re-fetching the post from the platform's API and checking it resolves.</p>
<p>Here's the real shape of it, from our Mastodon adapter:</p>
<pre><code class="language-ts">async verifyPublished(platformPostId: string, accessToken: string): Promise&lt;VerifyResult&gt; {
  const res = await fetch(`${DEFAULT_INSTANCE}/api/v1/statuses/${platformPostId}`, {
    headers: { Authorization: `Bearer ${accessToken}` },
  });
  const json = await res.json();

  if (!res.ok || json.id !== platformPostId) {
    return {
      verifiedLive: false,
      platformPostUrl: null,
      errorMessage: json.error ?? `Mastodon status verification failed (HTTP ${res.status})`,
    };
  }

  return {
    verifiedLive: true,
    platformPostUrl: json.url ?? `${DEFAULT_INSTANCE}/web/statuses/${platformPostId}`,
    errorMessage: null,
  };
}
</code></pre>
<p>Every platform adapter implements the same <code>verifyPublished()</code> contract. The scheduler calls it right after the post attempt, before it ever marks anything as done:</p>
<pre><code class="language-ts">const attempt = await adapter.post({ ...content });
if (!attempt.success || !attempt.platformPostId) {
  // handle failure — never reached "verified" state
}

// The post API call succeeding is NOT the same as the content being
// live — this read-back check is the actual differentiator, not an
// optional extra step.
const verification = await adapter.verifyPublished(attempt.platformPostId, accessToken);

if (!verification.verifiedLive) {
  // treated as a real failure, not a success with an asterisk
}
</code></pre>
<p>Only after that second call comes back clean does a post get marked <code>verified_live: true</code> and a customer-visible confirmation goes out. If it doesn't, it's a real failure — not a success with an asterisk quietly left off.</p>
<h2>Why this is worth the extra call</h2>
<p>It's one more HTTP round-trip per post, on top of the write itself. That's a real cost, and I get why most schedulers skip it — it only pays off when something's already gone wrong, which is exactly when it's easy to convince yourself "the API said it worked, so it's fine."</p>
<p>If you're building anything that writes to a third-party API and reports success back to a user, it's worth asking: does "success" mean <em>the call didn't error</em>, or does it mean <em>you went and checked</em>? Those are different guarantees, and only one of them is actually true when you say it.</p>
<hr />
<p><em>If you're curious about the rest of it — LazyRelay also ships an <a href="https://www.npmjs.com/package/@lazyrelay/mcp-server">MCP server</a> so an AI agent can schedule posts, pull this same verified-live data, and read mentions directly, without a human in the loop for the read side.</em></p>
<p><em>This post was originally published on <a href="https://dev.to/gosswerner1max/why-the-api-returned-200-isnt-proof-your-post-is-live-46kf">Dev.to</a>.</em></p>
]]></content:encoded></item></channel></rss>