通过 API 立即发布很简单。有意思的问题都出在排期上,因为一条已排期的帖子是对未来做出的一个承诺,而未来里包含夏令时、服务中断,以及你后来改变主意的帖子。
时区:一定要显式传递
最常见的排期漏洞,是一条帖子每年偏移一小时、一年发生两次。
一个排期时间需要两样东西:作为 ISO-8601 时间戳的那个具体时刻,以及它被表达时所处的时区,用类似 America/New_York 这样的 IANA 名称表示。
await bp.posts.create({
content: 'Morning update.',
channels: [{ channelId: 1, platform: 'linkedin' }],
status: 'scheduled',
scheduledAt: '2026-04-10T14:00:00Z',
timezone: 'America/New_York',
});
只存一个 UTC 时刻,对一次性的帖子来说没问题。但对任何周期性的任务来说都是错的,因为”每个工作日当地时间早上 9 点”在夏令时切换前后对应的是不同的 UTC 时刻。正是时区让这一点变得正确。
绝不要用一个像 -05:00 这样的原始偏移量来代替时区。偏移量会变,时区不会。
建模两种以上的状态
一条帖子不只是”已排期”或”已发布”这两种状态。至少要为以下这些状态做建模:
- draft,已创建但尚未加入队列
- scheduled,已加入队列,等待某个时间
- processing,已发送给平台,结果未知
- published,已确认成功上线
- failed,附带原因
- partial,部分目标发布成功,部分没有
人们常常忘记的两个是 processing 和 partial。
processing 之所以存在,是因为好几个平台会接受一条帖子并异步发布它。收到 200 不代表已经发布,真正的结果会在之后才到来。
partial 之所以存在,是因为一条帖子通常会面向多个平台。如果三个成功、一个失败,那么”published”和”failed”都不成立,硬把它归到任何一边,都会做出一个对用户撒谎的界面。
平台宕机时会发生什么
要为此做设计,因为它确实会发生。
临时性失败应该带退避重试。 一个平台返回 503 持续两分钟,不应该让帖子就此丢失。
重试必须是安全的。 一次超时的创建请求也许其实已经成功了。盲目重试会导致帖子重复,这比原本的失败更糟糕。重试前先核对一下。
永久性失败根本不应该重试。 一个过期的 token 或一条无效的文案不会自己修好,重试只会白白消耗配额,还会延迟通知的到达。
必须有人被告知。 凌晨三点一条已排期帖子的发布失败,如果你没让它发出声音,就会悄无声息。webhook 是正确的机制。
编辑和取消
一条已排期的帖子是一个你可能需要打破的承诺。你的集成应该支持修改时间、编辑内容,以及彻底取消。
需要注意,一旦帖子已经被平台接管,各平台在是否允许这些操作上做法不一,这也是为什么在你自己这一层做排期、临近发布时刻才提交给平台,会比把一条未来日期的帖子直接交给平台表现得更好的原因之一。
队列时段与明确时间
有两种排期模型,各有各的用武之地。
明确的时间,适用于一条帖子必须在某个特定时刻落地的场景:一次发布会、一场活动、一次协调好的公告。
队列时段,适用于持续稳定地少量发布的场景。你定义一个发布节奏,下一条帖子就占用下一个空闲时段,这样你就不用给一批帖子里的每一条都手动选一个时间戳。
对批量工作来说,第二种方式要省事得多,也正是这种模型让”把三十条帖子加入队列”变成一个合理的单一操作,而不是三十个决定。
周期性排期
这和一条已排期的帖子是不同的概念。一个周期性排期是一条规则,会按某个频率持续产生帖子:每日、每周、双周或每月。
有两件事需要在设计时考虑。时区在这里比前面提到的更重要,原因和夏令时一样。而且附加在一个周期性帖子上的媒体必须保留下来,而不能在发布后被清理掉,因为下一次这个排期还会再用到它。
套餐限制
| Free | Pro | Business | |
|---|---|---|---|
| 每日 API 请求数 | 30 | 5,000 | 50,000 |
| 帖子数量 | 每天 3 条 | 每天 30 条 | 无限 |
| 周期性排期数量 | 无 | 10 | 无限 |
简而言之
每一个排期时间戳都要附带一个 IANA 时区,尤其是任何周期性任务。把 processing 和 partial 建模成真实的状态,因为异步发布和多平台帖子确实会让这两种情况真实发生。对临时性失败要带退避重试,绝不盲目重试一次创建请求,并用 webhook 让凌晨三点的失败不会悄无声息。