投稿予約ツールを契約する前に、GASで作れないか?
ミドリ: タツヤさん、SNSの投稿予約ツールを検討しているんですけど、月額が思ったより高くて。しかも機能の8割は使わなさそうなんです。
タツヤ(nodding): よくある話だね。投稿数が月20〜40本くらいで、アカウントも2〜3個なら、GASで作ったほうが安いし柔軟だと思うよ。
ミドリ(surprised): GASでSNS投稿ができるんですか?
タツヤ: できる。各SNSがAPIを提供していて、GASからHTTPリクエストを投げるだけ。しかもスプレッドシートが管理画面になるのが一番のメリットなんだ。
ミドリ(thinking): 管理画面がスプレッドシート……。
タツヤ: 現場からすると、新しいツールの使い方を覚えなくていい。これが定着率に直結する。今日は作り方と、ハマりどころを話そう。
全体の構成
ミドリ: どういう仕組みになるんですか?
タツヤ: シンプルにするとこう。
[スプレッドシート]
投稿日時 / 本文 / 画像URL / 配信先 / 承認 / ステータス
↓
[GAS 時間主導型トリガー(15分ごと)]
↓ 配信時刻を過ぎた & 承認済み & 未投稿 の行を探す
↓
[各SNSのAPI] へ投稿
↓
[スプレッドシート] ステータスを「投稿済」に更新 + 投稿URLを記録
↓ 失敗時
[担当者へメール/チャット通知]
ミドリ: 15分ごとに見に行くんですね。
タツヤ(nodding): GASは常駐できないから、定期的に起動して該当行を探すという作り方になる。15分単位なら実用上まったく問題ない。
シート設計が9割
ミドリ: どこから作ればいいですか?
タツヤ: シート設計から。ここを丁寧にやると、後のコードが単純になる。
| 列 | 内容 | 備考 |
|---|---|---|
| A | 投稿予定日時 | 日時形式で入力 |
| B | 本文 | 改行込み |
| C | 画像URL | 省略可 |
| D | 配信先 | X / Threads など |
| E | 承認 | チェックボックス |
| F | ステータス | 未投稿/投稿済/失敗 |
| G | 投稿URL | 投稿後に自動記入 |
| H | エラー内容 | 失敗時に自動記入 |
ミドリ: 「承認」列があるんですね。
タツヤ: ここが実務では重要でね。作成者と承認者を分ける。担当者が本文を書いて、上長がチェックボックスを入れる。チェックが入っていない行は絶対に投稿されない。
ミドリ(thinking): 誤投稿の防止になりますね。
タツヤ: そう。しかもスプレッドシートの権限機能でそのまま実現できる。承認列だけ上長に編集権限を絞る、という運用もできる。専用ツールだと逆にこの柔軟さが出しにくい。
コードの骨格
ミドリ: 実際のコードはどのくらいの量になりますか?
タツヤ: 本体は50行くらい。骨格はこう。
function postScheduled() {
const sheet = SpreadsheetApp.getActive().getSheetByName("投稿予定");
const rows = sheet.getDataRange().getValues();
const now = new Date();
for (let i = 1; i < rows.length; i++) {
const [dt, body, imageUrl, target, approved, status] = rows[i];
if (status !== "未投稿") continue; // 既に処理済み
if (approved !== true) continue; // 未承認はスキップ
if (!(dt instanceof Date)) continue; // 日時未入力
if (dt > now) continue; // まだ時刻前
try {
const url = postToSns(target, body, imageUrl);
sheet.getRange(i + 1, 6).setValue("投稿済");
sheet.getRange(i + 1, 7).setValue(url);
} catch (err) {
sheet.getRange(i + 1, 6).setValue("失敗");
sheet.getRange(i + 1, 8).setValue(String(err));
notifyError(i + 1, err);
}
}
}
ミドリ: 条件をひとつずつ確認して、通ったものだけ投稿するんですね。
タツヤ(nodding): そう。先に弾く条件を並べる書き方にしておくと、後から条件を足しやすい。「特定の曜日は投稿しない」みたいな要件が来ても1行足すだけで済む。
トークンの置き場所
ミドリ: SNSのアクセストークンはどこに書くんですか?
タツヤ: 絶対にコードに書かない。スクリプトプロパティに入れる。
function getToken(target) {
const key = "TOKEN_" + target.toUpperCase();
const token = PropertiesService.getScriptProperties().getProperty(key);
if (!token) throw new Error("token not configured: " + key);
return token;
}
ミドリ: シートに書くのもダメですか?
タツヤ: ダメ。スプレッドシートは共有されるものだからね。閲覧権限を渡した瞬間にトークンも渡ることになる。スクリプトプロパティなら、シートを共有してもトークンは見えない。
必ずハマる4つのポイント
ミドリ: つまずきやすいところを先に知りたいです。
タツヤ: 4つある。全部一度は踏むと思っていい。
①二重投稿
タツヤ: いちばん多い。トリガーが重なったり、処理の途中でエラーが出たりすると、同じ行が2回投稿される。
ミドリ: どう防ぐんですか?
タツヤ: ステータスを先に書き換える。
// 投稿する前に「処理中」にしておく
sheet.getRange(i + 1, 6).setValue("処理中");
SpreadsheetApp.flush(); // 即座にシートへ反映
const url = postToSns(target, body, imageUrl);
sheet.getRange(i + 1, 6).setValue("投稿済");
ミドリ: 投稿してから更新するんじゃなくて、先に印を付ける。
タツヤ(nodding): そう。flush()を忘れると書き込みが遅延して意味がなくなるから、ここはセットで覚えておくといい。さらに厳密にするならLockServiceで排他制御を入れる。
②タイムゾーン
タツヤ: GASのプロジェクト設定と、シートのタイムゾーンがずれていると9時間ずれて投稿される。
ミドリ(surprised): 9時間……致命的ですね。
タツヤ: 深夜3時に投稿されて誰にも見られない、みたいなことが起きる。プロジェクトの設定でタイムゾーンを東京にするのを最初に確認しておく。
③画像の扱い
タツヤ: 画像付き投稿はテキストより手順が多い。多くのAPIでは画像を先にアップロードしてIDを取得し、そのIDを付けて投稿するという2段階になる。
ミドリ: 1回で送れないんですね。
タツヤ: そう。しかもサイズ制限と形式制限がある。GASの実行時間にも上限があるから、大きな画像を大量に処理するのは向かない。画像はDriveに置いて、共有URLを使う形が扱いやすい。
④APIの仕様変更
タツヤ: これが一番厄介。SNSのAPIは予告なく仕様が変わることがある。
ミドリ: 突然投稿できなくなると。
タツヤ(nodding): だから失敗通知は必須。黙って止まるのが最悪のパターンなんだ。「今週投稿されていない」と気づくのが1週間後、では遅い。
function notifyError(row, err) {
MailApp.sendEmail({
to: "[email protected]",
subject: "[SNS予約] 投稿に失敗しました(" + row + "行目)",
body: "エラー内容:\n" + err + "\n\nシートを確認してください。"
});
}
ミドリ: 通知があれば、すぐ手動で投稿できますね。
タツヤ: そう。自動化は止まる前提で作る。止まった時に人間が気づける仕組みまでがセットなんだ。
脱エンジニアの視点で考えると、「止まったことに何分で気づけるか」を設計できるかが分岐点になります。 動いている時の性能より、止まった時の検知速度のほうが、業務では価値が高いことが多いからね。
専用ツールを選ぶべきケース
ミドリ: 逆に、GASでは無理なケースもありますか?
タツヤ: ある。正直に言うと、この条件のどれかに当てはまるなら専用ツールのほうがいい。
| 条件 | 理由 |
|---|---|
| アカウントが10個以上 | 管理と権限が複雑になりすぎる |
| 月100本以上投稿する | シート運用の限界。実行時間も厳しい |
| 詳細な分析レポートが必要 | 分析機能を自作するコストが見合わない |
| 複数人が同時に下書きを編集する | 競合の制御が難しい |
| 動画投稿が中心 | ファイルサイズと処理時間の制約が厳しい |
ミドリ(thinking): うちは月30本、アカウント2つなので当てはまらないですね。
タツヤ: ならGASで十分。ツールは必要になってから契約すればいいし、その時にはシートに投稿データが溜まっているから移行も楽だよ。
まとめ
タツヤ: 今日の要点を整理しよう。
- 月40本・アカウント3個程度までならGASで十分実用になる
- スプレッドシートが管理画面になるので、現場が新しい操作を覚えなくていい
- 承認列(チェックボックス)を入れて作成者と承認者を分ける。誤投稿を構造的に防ぐ
- トークンはスクリプトプロパティへ。シートにもコードにも書かない
- 必ず踏む罠は二重投稿・タイムゾーン・画像の2段階処理・API仕様変更
- 二重投稿は投稿前にステータスを書き換えて
flush()で防ぐ - 失敗通知は必須。自動化は止まる前提で作る
ミドリ(smiling): 月額を払う前に、まず作ってみる価値はありそうです。
タツヤ: そう。作ってみると、自社が本当に必要な機能もはっきりする。それが分かってからツールを選べば、無駄なプランを契約せずに済むよ。
次のアクション
ミドリ: 何から手を付けましょう。
タツヤ: 3ステップで。
- シートの列設計を先に作る — 8列。コードより先にこちらを決めると迷わない
- 1アカウント・テキストのみで動かす — 画像は後回し。まず投稿が通ることを確認する
- 失敗通知を最初から入れる — 動いてから足すのではなく、最初から入れておく
タツヤ: Shimanto AI Solutionsでは、この構築を支援しているよ。
- 投稿運用フローの設計 — 承認体制を含めたシート設計と、権限の切り方を一緒に決める
- GAS実装と安全対策 — 二重投稿防止・トークン管理・失敗通知までを含めた実装を伴走
- 拡張と移行の判断支援 — 投稿量が増えた時に、専用ツールへ移るべきかを一緒に評価する
ミドリ: シート設計からですね。
タツヤ: そう。シートが決まればコードは自然に決まる。逆にすると必ず作り直しになるよ。
参考にした情報
本記事のコードは執筆時点(2026年8月)の仕様に基づきます。各SNSのAPIは予告なく仕様が変わるため、実装前に公式ドキュメントをご確認ください。
- Google Apps Script 公式ドキュメント — 時間主導型トリガー、
LockService、PropertiesService、実行時間の上限 - 各SNSの開発者向け公式ドキュメント — 投稿エンドポイント・画像アップロード手順・レート制限の一次情報