A Timed-Out Publish Call Can Post Twice
Three checks that decide whether an approval gate still holds when the request fails, and a test to run on a throwaway account

*Image: Social media apps by Theresepersonne, licensed under CC BY-SA 4.0.*
An approval gate has one job at publish time: make sure exactly one post appears. The request that carries that post is a POST, and the IETF draft on the Idempotency-Key header notes that POST and PATCH are not idempotent methods (draft-ietf-httpapi-idempotency-key-header-07, published 15 October 2025). A queue that retries after a timeout can therefore publish approved text twice while every step of the approval process did what it was told.
That gap is worth closing before you hand an agent the publish button, because the failure is quiet. Nothing in a doubled post looks like an error: the calendar shows one item, the approval record shows one approval, and the account shows two posts.
What the client knows after a timeout
A publish call that times out gives the client no information about the resource. The draft describes the case directly: the client sent a POST, the request timed out, and the client "is left uncertain about the status of the resource. It doesn't know if the resource was created or updated, or if the server even completed processing the request. Furthermore, the client does not know if it can safely retry the request" (IETF draft, section 1).
Two very different situations produce the same silence: a request that never reached the server, where a retry is the correct move, and a request the server completed before the response was lost, where a retry creates a second post. A scheduler that treats both as "failed" will get the second case wrong at some rate, and the rate grows with retries and slow responses.
Mastodon's documented defaults show how easily the second case appears. The API allows 300 calls within 5 minutes per account and per IP, and returns X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers so a client can see how close it is to the ceiling (Mastodon rate limits, captured 22 October 2026). A retry loop that ignores those headers keeps sending into a limit that is already saturated. Each refusal adds another round of uncertainty about which earlier attempt landed.
Check one: a key the platform honors
Mastodon documents the clearest case: the statuses endpoint accepts an Idempotency-Key header, described as "any arbitrary string to prevent duplicate submissions of the same status," with the advice to use "a hash or UUID generated client-side," and the keys "are stored for up to 1 hour" (Mastodon statuses API, captured 22 October 2026). A retry that carries the same key inside that hour is treated as the same submission.
The key only works if it is stable, and the draft makes that a hard rule: the key "MUST be unique and MUST NOT be reused with another request with a different request payload" (IETF draft, section 2.2). That rule is easy to break by accident. If the key is generated inside the retry function, every attempt gets a fresh key and the protection disappears. If the body is re-rendered between attempts, a shortened link, a fresh timestamp or a different trailing line makes it a different payload, and reusing the key then violates the rule the platform is relying on.
The practical fix is to generate the key when the draft is created, store it next to the post, and hash the exact body at the same moment. A retry sends the stored key and the stored bytes, or it does not send anything.
Where the key runs out
One hour is a short window for a publishing queue. Mastodon's scheduled_at parameter must be at least 5 minutes in the future, and a scheduled post returns a ScheduledStatus rather than a Status (Mastodon statuses API). At the scheduled minute the platform publishes without a further client request, so the key from the original call plays no part. A client retry the next morning, after a queue pause or a weekend, sits outside the stored window as well.
So the key answers the fast failure and leaves the slow one open. Ask the tool two questions about its own behavior: how long it waits before retrying, and what it does when that wait is longer than the platform's key lifetime. A tool that retries within the hour and reuses the stored key is covered on Mastodon. A tool that retries when someone opens the dashboard the next day is not.
Check two: a read-back before any retry
A lookup answers the same question without a key: after an ambiguous failure, read the account's recent posts and compare them with what you tried to publish before sending again. Mastodon exposes GET /api/v1/accounts/:id/statuses for exactly this kind of read (Mastodon accounts API, captured 22 October 2026).
A read-back needs a known address to look at, and AT Protocol shows why that matters. The Bluesky createRecord call takes an optional rkey, so the client can fix the record's address before the request is sent (createRecord lexicon, captured 22 October 2026). The record-key specification says the "any" key type "may be used to encode semantics in the name" and that this "enables de-duplication and known-URI lookups," and it states that the tuple of account, collection and record key is unique (AT Protocol record keys, captured 22 October 2026). Record the key before the call, then a read-back can look up that exact address instead of scanning the account for something similar.
A hosted scheduler can offer the same property through its own identifiers. The SMM Agent posting docs describe creating a draft with POST /api/posts, where intent defaults to draft, and publishing a saved post with POST /api/posts/POST_ID/publish; the page says publishing runs the connected platform pipeline for the saved targets and to keep that call behind explicit user approval (SMM Agent posting docs, captured 22 October 2026). The saved post id is an address that exists before anything reaches a social platform, which is what a read-back needs. The page does not state whether that publish call is idempotent, so do not assume either answer before you test it.
Check three: the approval covers the bytes
An approval that names a draft can outlive the text it approved. If the body is edited after approval, or the agent regenerates a paragraph while the post waits in the queue, the approval still looks valid because it points at a record rather than at content.
Store a hash of the exact body at approval time, and compare it immediately before the publish call. When the hashes differ, the approval is stale and the post goes back for review. This is also what makes a post-publish receipt checkable: the record can carry the hash of what was approved, the platform id of what was published, and the key that was used, so a mismatch is visible without reading the whole thread.
The SMM Agent home page describes the same shape from the product side, saying posts ship with source-backed drafts, approval receipts and platform confirmation (SMM Agent home page, captured 22 October 2026). Receipts are only useful if they contain enough to compare, which means the approved hash, the returned platform id and the timestamp of the call.
The test to run on a throwaway account
Documentation describes the platform, and your tool decides what it sends, so run the check once on an account that has no audience; twenty minutes is enough.
- Create a test account on the platform you care about and connect only that account to the tool.
- Create one draft whose text carries a unique marker, such as a run id and the minute you started.
- Find out what the tool sends: the key header if it sends one, or the fact that it sends none.
- Force the ambiguous failure. Block the response after the request has left, for example with a local proxy that drops the reply, or stop the connection while the call is in flight. A client-side abort is a weaker test, since the server may finish the work anyway.
- Let the queue do whatever it does by default. Then count the posts on the test account that carry the marker.
- Repeat with the response delayed past the tool's own timeout, and once more after the platform's key window has passed.
- Read the tool's log for three things: one key or several, a read-back call before the retry or none, and what the approval record stored.
Both outcomes are informative: a single post with one key in the log means the failure path is covered for fast retries. A second post with a fresh key means the duplicate risk is real for that tool, and the read-back and hash checks are the parts you need to add or ask for.
Keep the result next to the runbook, not in a chat thread. The numbers that matter are the ones you will need the next time a post appears twice: the key, the body hash, the platform id, and the time the call was made.
A test that produces a second post leaves the tool with one of two fixes to make before it can publish unattended: store an idempotency key with the draft and reuse it on every attempt, or write the record address down before the call and read it back after a failure. Ask which of the two the tool has, and put the answer next to the posting runbook with the date you checked it.
Ready to automate your social posting?
Join the waitlist for early access to SMMClaw.