Trigger.dev
Run scheduled social publishing and delivery reconciliation as durable Trigger.dev tasks with Social SDK idempotency keys and explicit outcomes.
Social SDK deliberately owns no scheduler: nothing in the SDK fires later, polls, or retries an uncertain public write on its own. Trigger.dev supplies that missing piece as durable tasks with retries and delays you configure, while the SDK keeps every platform call explicit.
Install
npm install @opencoredev/social-sdkpnpm add @opencoredev/social-sdkyarn add @opencoredev/social-sdkbun add @opencoredev/social-sdknub add @opencoredev/social-sdkaube add @opencoredev/social-sdkScheduled publish as a task
Store the draft and its target account in your database, then let the task publish at the scheduled moment. The draft ID doubles as the idempotency key, so a retried task run cannot double-post.
import { task } from "@trigger.dev/sdk/v3";
import { createSocial, connectedAccountRef } from "@opencoredev/social-sdk";
import { threads } from "@opencoredev/social-sdk/threads";
const social = createSocial({
backend: threads({
auth: {
userId: process.env.THREADS_USER_ID!,
accessToken: process.env.THREADS_ACCESS_TOKEN!,
},
}),
});
export const publishScheduled = task({
id: "publish-scheduled-post",
retry: { maxAttempts: 3 },
run: async (payload: { draftId: string }) => {
const draft = await loadDraft(payload.draftId); // your storage
if (draft.status !== "scheduled") return { skipped: true };
const result = await social.posts.publish({
targets: [
{
account: connectedAccountRef({
backend: "default",
platform: "threads",
accountId: draft.accountId,
}),
},
],
content: { text: draft.text },
idempotencyKey: payload.draftId,
});
await saveOutcomes(payload.draftId, result.outcomes); // your storage
return { status: result.status };
},
});
Trigger it with a delay when the user schedules:
await publishScheduled.trigger({ draftId }, { delay: draft.publishAt });
Reconciliation as a scheduled task
Platforms like Threads, Instagram, TikTok, and YouTube return processing states. Reconcile them on a schedule instead of polling inline:
import { schedules } from "@trigger.dev/sdk/v3";
export const reconcileDeliveries = schedules.task({
id: "reconcile-deliveries",
cron: "*/5 * * * *",
run: async () => {
const pending = await loadPendingDeliveries(100); // state processing/unknown
for (const row of pending) {
const outcome = await social.posts.getDelivery(row.ref);
await saveDeliveryState(row.id, outcome.state);
}
},
});
Each getDelivery call is an explicit read; nothing in the SDK re-fires the publish.
Notes
- Trigger.dev tasks run on Node, a tested SDK runtime; keep the SDK client at module scope so warm task runs reuse it.
- Keep platform tokens in Trigger.dev environment variables, scoped per deployment environment.
- Task retries plus SDK idempotency keys are the supported pattern for at-least-once schedulers publishing at-most-once content.