投稿予約ツールを契約する前に、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で十分ツールは必要になってから契約すればいいし、その時にはシートに投稿データが溜まっているから移行も楽だよ。

まとめ

タツヤ: 今日の要点を整理しよう。

ミドリ(smiling): 月額を払う前に、まず作ってみる価値はありそうです。

タツヤ: そう。作ってみると、自社が本当に必要な機能もはっきりする。それが分かってからツールを選べば、無駄なプランを契約せずに済むよ。

次のアクション

ミドリ: 何から手を付けましょう。

タツヤ: 3ステップで。

  1. シートの列設計を先に作る — 8列。コードより先にこちらを決めると迷わない
  2. 1アカウント・テキストのみで動かす — 画像は後回し。まず投稿が通ることを確認する
  3. 失敗通知を最初から入れる — 動いてから足すのではなく、最初から入れておく

タツヤ: Shimanto AI Solutionsでは、この構築を支援しているよ。

ミドリ: シート設計からですね。

タツヤ: そう。シートが決まればコードは自然に決まる。逆にすると必ず作り直しになるよ。


参考にした情報

本記事のコードは執筆時点(2026年8月)の仕様に基づきます。各SNSのAPIは予告なく仕様が変わるため、実装前に公式ドキュメントをご確認ください。

関連記事

シマント AI のサービス | 脱エンジニアの部屋 | AI戦略マップ 2025–2045