CodexにX投稿を手伝わせる―未投稿記事を管理してWeb Intentで半自動化する
ゆまラボの記事をXへ流す方法として、Codexに未投稿記事の抽出、投稿文生成、UTM付きURL作成、Web Intent起動まで任せ、最後のポストだけ人が確認する半自動運用を考えます。

記事をSNSへ流す運用を始める―X連携の前に投稿方法と候補を確認するでは、Xへの投稿方法としてWeb IntentとX APIの2つを確認した。
X APIなら投稿まで完全に自動化できる。
ただ、現在のX APIはpay-per-usageで、アプリ作成やOAuthも必要になる。ゆまラボで最初にやりたいことは、そこまで大きな仕組みではない。
記事を公開したあと、
まだXへ流していない記事を見つける
↓
投稿文を作る
↓
UTM付きURLを作る
↓
Xの投稿画面を開く
↓
内容を確認してポストする
ここまでできれば十分。
この作業なら、CodexとX Web Intentを組み合わせる方法がかなり合いそうだ。
完全自動投稿ではなく、準備と判定はCodex、最後のポストだけ人間にする。
実装はまだ行っていない。今回は、この方法ならどう作るかを先に決める。
Web IntentならX APIの認証を持たなくていい#
X公式のWeb Intentsは、サイトからXの投稿画面を開くための仕組み。
現在の公式ドキュメントでは、
https://x.com/intent/tweet
を使える。
textで投稿文を事前入力でき、urlで共有するURLも指定できる。
例えば、
https://x.com/intent/tweet
?text=Astroブログの記事を公開しました
&url=https://example.com/blog/example/
のような形。
実際には日本語やURLをURLエンコードして使う。
Web Intentの大きな違いは、X APIのようにDeveloper Appや投稿用アクセストークンを持たなくてよいこと。
X公式も、Web IntentではXアプリの設定、認証情報の保存、投稿前のアプリ権限取得は不要としている。
その代わり、投稿そのものは自動ではない。
X側の投稿画面が開き、ユーザーが内容を確認して投稿する。
ここは今回の用途ではむしろ都合がいい。
AIに投稿文を作らせても、最後だけ目で確認できる。
Codexには投稿済みURLを覚えさせる#
毎回人間が、
この記事はもうXへ投稿したっけ
と確認するのは面倒。
そこで、Xへ流した記事をリポジトリ内のファイルで管理する。
例えば、
data/
└─ sns/
└─ x-posted.json
を作る。
内容は最初から複雑にしない。
{
"version": 1,
"posts": [
{
"articleUrl": "https://bringain.com/blog/sns-publishing-candidates/",
"status": "posted",
"postedAt": "2026-09-12T10:30:00+09:00",
"xUrl": "https://x.com/example/status/1234567890"
}
]
}
Codexはsrc/content/blog/を見れば、現在の記事を取得できる。
そこから公開URLを組み立て、x-posted.jsonと比較する。
記事一覧
↓
公開済みURLを作る
↓
x-posted.jsonと照合
↓
記録がない記事
↓
X未投稿候補
という処理。

※ 記事内の図は内容を整理するために作成した概念図です。各社の公式資料や実際の製品画面ではありません。
投稿済みURLをXから毎回検索するより、ゆまラボ側で「どの記事を投稿したか」を持っておく方が単純。
将来BlueskyやThreadsも使うなら、
x-posted.json
bluesky-posted.json
threads-posted.json
と分ける方法もある。
最初はXだけでよい。
Codexには投稿文まで作らせる#
未投稿記事を見つけるだけなら、普通のNode.jsスクリプトでもできる。
Codexを使う意味が出るのは、その先。
記事の、
title
description
本文
tags
を読ませて、X用の投稿文を作らせる。
例えば記事タイトルが、
PCでは問題ないのにスマホで画像表示が止まる―
重い画像が与える影響とAstroでの対策
なら、そのまま全部投稿するのではなく、
PCでは問題ないのに、スマホでは画像付近で止まる。
ゆまラボでAstroの画像最適化を試し、
srcset・WebP・遅延読み込みまで見直した記録です。
https://bringain.com/blog/mobile-heavy-images/...
のように、記事内容に合わせて短くできる。
ここで毎回同じ定型文へ押し込むより、記事ごとに少し変えた方が自然。
ただしCodexへ自由に書かせるだけだと、本文にない効果や数字を足す可能性もある。
投稿文の生成条件は限定する。
記事本文に書かれている内容だけを使う
未確認の効果を追加しない
煽る表現を使わない
投稿文は短くする
記事URLを必ず付ける
このくらいでよさそうだ。
URLにはX用のUTMを付ける#
前の記事では、SNSからの流入をGoogle Analyticsで分けるためにUTMを使うことにした。
Xへ投稿するURLは、そのままの記事URLではなく、
https://example.com/blog/example/
?utm_source=x
&utm_medium=social
&utm_campaign=article_publish
を使う。
これもCodex側で毎回作ればよい。
人間がURLをコピーし、
?utm_source=x...
を手で足す必要はない。
投稿管理用のarticleUrlには通常URLを保存し、Web Intentへ渡すときだけUTMを付ける。
管理用
https://example.com/blog/example/
X投稿用
https://example.com/blog/example/?utm_source=x&utm_medium=social&utm_campaign=article_publish
と分ける。
この方が、「同じ記事なのにUTM違いで別URLとして管理してしまう」状態を避けられる。
WindowsならCodexからWeb Intentを開ける#
ゆまラボの作業環境はWindows。
Web Intent URLを作ったあと、PowerShellならブラウザを開ける。
Start-Process "https://x.com/intent/tweet?text=..."
Codexはローカルでコマンドを実行できるため、許可された範囲ならこの処理まで任せられる。
Codexの非対話モードにはcodex execもあり、スクリプトやパイプラインへ組み込む用途が公式に用意されている。
ただ、今回の最初の形ではcodex execまで持っていかなくてもよいと思う。
対話中に、
Xへ未投稿の記事を1件出して
と頼む。
Codexが、
未投稿記事を検出
↓
投稿文を作成
↓
UTM URLを作成
↓
Web Intentを開く
ところまで進める。
その方が確認しやすい。
運用が安定してから、未投稿候補の抽出や投稿文生成だけをスクリプト化しても遅くない。
Intentを開いただけでは投稿済みにしない#
この仕組みで一番気を付けたいのはここ。
CodexからWeb Intentを開くことには成功した。
でも、
投稿画面を見て閉じた
だけかもしれない。
その状態でx-posted.jsonをpostedへ変更すると、実際には投稿していない記事が「投稿済み」になる。
なので、
未投稿
↓
CodexがWeb Intentを開く
↓
人が投稿内容を確認
↓
Xでポスト
↓
人がCodexへ「投稿した」と伝える
↓
x-posted.jsonを更新
にする。
投稿後のX URLも分かるなら保存する。
{
"articleUrl": "https://example.com/blog/example/",
"status": "posted",
"postedAt": "2026-09-12T10:30:00+09:00",
"xUrl": "https://x.com/example/status/..."
}
これなら後から、
この記事はいつXへ流したか
実際のPostはどれか
も確認できる。
Web Intentは、X公式でもユーザーが投稿画面を見て、最終的に投稿するか判断する仕組みとして案内されている。
その設計を無理に飛ばさない。
最初は半自動の方がゆまラボには合う#
X APIを使えば、
記事公開
↓
自動投稿
まで持っていける。
ただし、Developer App、OAuth、アクセストークン、API利用料金を管理することになる。
Web Intent方式なら、
記事管理
投稿済み判定
投稿文生成
UTM生成
ブラウザ起動
までは自動化できる。
最後の1回だけ自分でポストする。
記事公開の頻度を考えると、まずはこの方が扱いやすそうだ。
自動化したいのは「1秒でも人間操作をなくすこと」ではない。
投稿済みか覚えておく
投稿文を毎回考える
URLを組み立てる
Xの投稿画面を開く
という細かい作業を減らすこと。
最後の内容確認は残してよい。
実際に作るなら、次は、
data/sns/x-posted.jsonを追加
↓
Codex用の指示を決める
↓
未投稿記事を抽出
↓
Web Intentを実際に開く
↓
Xへテスト投稿
↓
投稿済み管理が更新されることを確認
まで試す。
そこで問題がなければ、X投稿の半自動運用として使えそうだ。


