Upload to Slack
Your app owns posting. Build the complete envelope before the first post attempt:
import type { ConversionResult } from "slackmark";
import type { NodeConverter } from "slackmark-node";
interface PreparedSlackPost {
readonly channel: string;
readonly threadTs?: string;
readonly blocks: ConversionResult["blocks"];
readonly text: string;
readonly receipts: ConversionResult["receipts"];
}
declare const converter: NodeConverter;
declare const markdown: string;
declare const channel: string;
declare const threadTs: string | undefined;
declare function postPrepared(
prepared: PreparedSlackPost,
options: { readonly timeoutMs: number },
): Promise<void>;
const result = await converter.convert(markdown, { timeoutMs: 30_000 });
const prepared: PreparedSlackPost = {
channel,
...(threadTs === undefined ? {} : { threadTs }),
blocks: result.blocks,
text: result.text,
receipts: result.receipts,
};
await postPrepared(prepared, { timeoutMs: 10_000 });On post failure, return or throw this exact envelope and retry posting that.
Persist prepared -> posted only when your app needs crash recovery. That persistence
still does not make Slack posting idempotent. slackmark provides no outbox, worker,
database, or posting API.
You need files:write for the default external upload flow and chat:write for your own
posting. Add files:read only when waitForProcessing: true.
FetchSlackUploader defaults:
| Option | Default |
|---|---|
| Per-stage request timeout | 10 seconds |
| Processing polling | off |
| Poll interval / attempts / total window | 250 ms / 20 / 30 seconds |
channelId | absent (private completion) |
| Retry / idempotency / orphan cleanup | none |
An upload moves through five stages: admission, upload-URL allocation, byte upload,
completion, and an optional file-info fetch. A rejection at admission carries phase
requested and retry safety safe, because the uploader was never invoked.
Receipts record phase, file ID, sharing, processing, status, Retry-After, provider code,
and retry safety. may-duplicate requires reconciliation; never means reuse the
completed result. A polling failure after completion returns
processing: "unverified" and never triggers replacement upload.
Whether to re-post a message, whether to re-upload a file (retrySafety), and whether to
re-run a conversion are three separate questions, each with its own signal.
Do not log receipts or source-bearing URLs broadly. Files complete without a channelId.
Confirm in your own workspace that a privately completed file renders for every viewer you
expect.