LINEミニアプリからサービスメッセージを送信する方法

LINEミニアプリからサービスメッセージを送信する方法

2026.02.05

リテールアプリ共創部のるおんです。
先日、LINEミニアプリの開発において サービスメッセージ を実装する機会がありました。今回は、LINEミニアプリのサービスメッセージとは何か、メッセージングAPIとの違い、そして実際に実装してみた内容を共有したいと思います。

LINEミニアプリのサービスメッセージとは

https://developers.line.biz/ja/docs/line-mini-app/develop/service-messages/

サービスメッセージ とは、ユーザーがLINEミニアプリ上で行った操作に対する確認や応答として送信される通知機能です。例えば、予約完了の通知やリマインド、会員登録完了のお知らせなど、ユーザーが知っておくべき重要な情報を伝えることができます。

サービスメッセージは、LINEアプリ内の専用トークルーム「LINEミニアプリ お知らせ」に表示されます。

IMG_4641

これは認証済みのLINEミニアプリ専用の機能です。

LINE公式アカウントからのメッセージ配信との違い

LINEでメッセージを送信する方法として、よく知られているのはLINE公式アカウントからのメッセージ配信です。これはLINE公式アカウントマネージャーから手動で配信したり、メッセージングAPIを使ってプログラムから配信したりできます。では、サービスメッセージとは何が違うのでしょうか。

項目 サービスメッセージ LINE公式アカウントからの配信
料金 無料 従量課金制
送信回数制限 同一操作に対して最大5回まで プランに応じた制限
メッセージ形式 テンプレート形式のみ 自由形式
表示場所 専用トークルーム(LINEミニアプリ お知らせ) 公式アカウントのトーク
対象 認証済みLINEミニアプリのみ LINE公式アカウント

配信先

サービスメッセージ LINE公式アカウントからの配信
IMG_4641 IMG_4642

サービスメッセージの大きな特徴は以下の4点です:

  1. 無料で利用可能:LINE公式アカウントからのメッセージ配信は送信数に応じて課金されますが、サービスメッセージは無料です。
  2. 送信回数の制限:同じ操作に対して最大5回までしか送信できません。これにより、スパム的な利用が防止されています。
  3. テンプレート形式:LINEヤフー株式会社が提供するテンプレートを使用します。自由にメッセージを作成することはできませんが、審査済みのテンプレートを使用することでユーザー体験の一貫性が保たれます。
  4. LINE公式アカウントがブロックされていても送信可能:ユーザーがLINE公式アカウントをブロックしていても、サービスメッセージは送信できます。これは専用トークルーム「LINEミニアプリ お知らせ」に配信されるためです。

全体像

今回実装したシステムのアーキテクチャは以下の通りです。

line-mini-app-service-message-architecture-ページ1.drawio

  • フロントエンド:React + TypeScript(S3 + CloudFrontでホスティング)
  • バックエンド:Express.js + TypeScript(Lambda + API Gateway)
  • データベース:DynamoDB

フロントエンドのLINEミニアプリからLIFFアクセストークンを含めてAPIリクエストを送信し、バックエンドでサービス通知トークンの発行とサービスメッセージの送信を行います。

やってみた

それでは、実際にサービスメッセージを送信する実装を見ていきましょう。

LINE Developersコンソールでの設定

まず、サービスメッセージを送信するためにLINE Developersコンソールで事前設定を行います。

https://developers.line.biz/ja/

1. チャネルIDとチャネルシークレットの取得

LINE Developersコンソールにアクセスし、対象のLINEミニアプリチャネルを選択します。
「チャネル基本設定」タブから以下の情報を取得します。

  • チャネルID:チャネルを識別するための一意のID
  • チャネルシークレット:チャネルアクセストークンの発行に必要な秘密鍵

これらの値はバックエンドでLINE APIを呼び出す際に使用するため、安全に管理する必要があります。今回は環境変数から呼び出すようにしています。

2. サービスメッセージテンプレートの登録

次に、送信するサービスメッセージのテンプレートを登録します。

スクリーンショット 2026-02-05 19.38.18

  1. LINE Developersコンソールで対象チャネルの「サービスメッセージ」タブを開く
  2. 「テンプレートを追加」をクリック
  3. 使用したいテンプレートカテゴリを選択(例:予約、購入完了、リマインドなど)
  4. テンプレートの言語を選択(日本語、英語など6言語対応)
  5. テンプレート名を設定(APIから呼び出す際に使用)

テンプレートにはLINEヤフー株式会社が用意したものを使用します。テンプレート内の変数(例:${number}${reg_date})には、API呼び出し時に任意の値を設定できます。

3. LIFFアプリの設定確認

サービスメッセージを送信するには、フロントエンドからLIFFアクセストークンを取得する必要があります。LIFFアプリが正しく設定されていることを確認してください。

処理の流れ

サービスメッセージを送信するには、以下の3つのステップが必要です:

  1. チャネルアクセストークンの発行
  2. サービス通知トークンの発行
  3. サービスメッセージの送信

mini-illust-01-ja (2)

それぞれのステップを順番に解説していきます。

フロントエンド(React)

フロントエンドでは、LIFF SDKを初期化した後、liff.getAccessToken()でLIFFアクセストークンを取得し、バックエンドAPIに送信します。ポイントはLIFFアクセストークンをヘッダーに含めてバックエンドに送信することです。

SendMessageButton.tsx
import liff from "@line/liff";

const sendServiceMessage = async (params: Record<string, string>) => {
  // LIFFアクセストークンを取得
+ const liffAccessToken = liff.getAccessToken();

  if (!liffAccessToken) {
    throw new Error("LIFFアクセストークンが取得できませんでした");
  }

  const response = await fetch("https://your-api-endpoint.com/send-message", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
+     "X-LIFF-Access-Token": liffAccessToken,
    },
    body: JSON.stringify({ params }),
  });

  if (!response.ok) {
    throw new Error("サービスメッセージの送信に失敗しました");
  }

  return response.json();
};

// 使用例
const SendMessageButton = () => {
  const handleClick = async () => {
    try {
      await sendServiceMessage({
        "1": "変数1の値",
        "2": "変数2の値",
      });
      alert("メッセージを送信しました");
    } catch (error) {
      console.error(error);
      alert("送信に失敗しました");
    }
  };

  return <button onClick={handleClick}>サービスメッセージを送信</button>;
};

liff.getAccessToken()で取得したLIFFアクセストークンをX-LIFF-Access-Tokenヘッダーに設定してサーバーに送信しています。このトークンがサービス通知トークンの発行に必要になります。

バックエンド(Lambda関数)

バックエンドでは、API Gateway + Lambdaの構成でサービスメッセージを送信します。
以下は、一連の処理をまとめたLambdaハンドラーの実装例です。

handler.ts
import { ServiceNotificationTokenRepository } from "./service-notification-token-repository";

const CHANNEL_ID = process.env.LINE_CHANNEL_ID!;
const CHANNEL_SECRET = process.env.LINE_CHANNEL_SECRET!;
+const TEMPLATE_NAME = "restock_d_ja"; // LINE Developersコンソールで設定したテンプレート名

const tokenRepository = new ServiceNotificationTokenRepository();

/**
 * サービスメッセージ送信Lambda関数
 */
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
  try {
    // リクエストからLIFFアクセストークンとパラメータを取得
+   const liffAccessToken = event.headers["x-liff-access-token"];
    if (!liffAccessToken) {
      return {
        statusCode: 401,
        body: JSON.stringify({ error: "LIFF access token is required" }),
      };
    }

    const body = JSON.parse(event.body || "{}");
+   const { userId, params } = body;

    // 1. チャネルアクセストークンを発行
+   const channelAccessToken = await getChannelAccessToken();

    // 2. サービス通知トークンを発行
+   const tokenResponse = await issueServiceNotificationToken(
+     channelAccessToken,
+     liffAccessToken
+   );
    console.log("サービス通知トークン発行完了", {
      remainingCount: tokenResponse.remainingCount,
    });

    // 3. サービスメッセージを送信
+   const sendResponse = await sendServiceMessage(
+     channelAccessToken,
+     tokenResponse.notificationToken,
+     TEMPLATE_NAME,
+     params
+   );
    console.log("サービスメッセージ送信完了", {
      remainingCount: sendResponse.remainingCount,
    });

    // 4. サービス通知トークンをDBに保存(次回以降の再利用のため)
+   await tokenRepository.save({
+     userId,
+     notificationToken: sendResponse.notificationToken,
+     remainingCount: sendResponse.remainingCount,
+   });

    return {
      statusCode: 200,
      body: JSON.stringify({
        success: true,
        remainingCount: sendResponse.remainingCount,
      }),
    };
  } catch (error) {
    console.error("サービスメッセージ送信エラー:", error);
    return {
      statusCode: 500,
      body: JSON.stringify({ error: "Internal server error" }),
    };
  }
};

/**
 * 1: チャネルアクセストークンを発行する
 * @see https://developers.line.biz/ja/reference/messaging-api/#issue-stateless-channel-access-token
 */
const getChannelAccessToken = async (): Promise<string> => {
  const response = await fetch("https://api.line.me/oauth2/v3/token", {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: `grant_type=client_credentials&client_id=${CHANNEL_ID}&client_secret=${CHANNEL_SECRET}`,
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(`チャネルアクセストークンの発行に失敗: ${response.status} ${errorText}`);
  }

  const data = await response.json();
  return data.access_token;
};

/**
 * 2: サービス通知トークンを発行する
 * @see https://developers.line.biz/ja/reference/line-mini-app/#issue-notification-token
 */
const issueServiceNotificationToken = async (
  channelAccessToken: string,
  liffAccessToken: string
): Promise<{
  notificationToken: string;
  expiresIn: number;
  remainingCount: number;
  sessionId: string;
}> => {
  const response = await fetch("https://api.line.me/message/v3/notifier/token", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${channelAccessToken}`,
    },
    body: JSON.stringify({ liffAccessToken }),
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(`サービス通知トークンの発行に失敗: ${response.status} ${errorText}`);
  }

  return await response.json();
};

/**
 * 3: サービスメッセージを送信する
 * @see https://developers.line.biz/ja/reference/line-mini-app/#send-service-message
 */
const sendServiceMessage = async (
  channelAccessToken: string,
  notificationToken: string,
  templateName: string,
  params: Record<string, string>
): Promise<{
  notificationToken: string;
  remainingCount: number;
  sessionId: string;
}> => {
  const response = await fetch(
    "https://api.line.me/message/v3/notifier/send?target=service",
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${channelAccessToken}`,
      },
      body: JSON.stringify({
        notificationToken,
        templateName,
        params,
      }),
    }
  );

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(`サービスメッセージの送信に失敗: ${response.status} ${errorText}`);
  }

  return await response.json();
};

処理の解説

上記のコードでは、以下の流れでサービスメッセージを送信しています。

1. チャネルアクセストークンを発行
LINE APIを呼び出すために必要なチャネルアクセストークンを発行します。

https://developers.line.biz/ja/reference/messaging-api/#issue-stateless-channel-access-token

POST https://api.line.me/oauth2/v3/token

チャネルIDとチャネルシークレットは環境変数から読み込んでいます。

2. サービス通知トークンを発行
フロントエンドから送信されたX-LIFF-Access-Tokenヘッダーから取得した LIFFアクセストークン と、先ほど取得した チャネルアクセストークン を使用して、サービス通知トークンを発行します。

https://developers.line.biz/ja/reference/line-mini-app/#issue-notification-token

POST https://api.line.me/message/v3/notifier/token

レスポンスには以下の情報が含まれます:

  • notificationToken:サービスメッセージ送信に使用するトークン
  • expiresIn:トークンの有効期限(秒)。最大1年間有効
  • remainingCount:残り送信可能回数(最大5回)
  • sessionId:セッションID

3. サービスメッセージを送信
サービス通知トークンを使用して、サービスメッセージを送信します。

https://developers.line.biz/ja/reference/line-mini-app/#send-service-message

POST https://api.line.me/message/v3/notifier/send?target=service

templateNameにはLINE Developersコンソールで設定したテンプレート名(今回はrestock_d_ja)を、paramsにはテンプレート内の変数に渡す値を設定します。テンプレート名はバックエンドで定数として管理しています。

4. サービス通知トークンをDBに保存
送信後のレスポンスに含まれるnotificationTokenremainingCountをDynamoDBに保存します。これにより、次回以降のメッセージ送信時にトークンを再利用できます。

認証済みミニアプリと未認証ミニアプリについて

サービスメッセージを 本番環境 で利用するためには、LINEミニアプリの審査に通過して「認証済みミニアプリ」になる必要があります。しかし、開発環境であれば未認証のミニアプリでもサービスメッセージの実装・動作確認が可能 です。

本番環境(認証済みミニアプリ)

  • LINEヤフー株式会社による審査に通過する必要がある
  • 審査には数日〜数週間かかる場合がある
  • 実際のユーザーにサービスメッセージを送信できる
  • テンプレートも審査を通過したものしか使用できない

開発環境(未認証ミニアプリ)

  • 審査なしでサービスメッセージの実装・テストが可能
  • 開発者自身や権限を持つテスターにのみメッセージが送信される
  • 本番リリース前に動作確認ができる
  • テンプレートのテストも可能

つまり、開発フェーズでは審査を待たずにサービスメッセージの実装を進めることができます。実装が完了してから審査を申請し、審査通過後に本番環境へデプロイするという流れで開発を進められます。

これにより、「審査に通るまで実装できない」という心配をせずに、先に実装を完了させておくことが可能です。

注意点

サービスメッセージを実装する際の注意点をまとめます。

1. 本番環境では認証済みLINEミニアプリが必要

本番環境でサービスメッセージを利用するには、認証済みのLINEミニアプリである必要があります。開発環境では未認証でも動作確認可能ですが、実際のユーザーへの送信には審査通過が必須です。

2. テンプレートの事前登録が必要

使用するテンプレートはLINE Developersコンソールで事前に登録し、審査を通過する必要があります。チャネルごとに最大20個まで登録可能です。

3. 送信回数の制限

同じ操作に対して最大5回までしか送信できません。remainingCountを確認して、残り送信可能回数を把握しておくことが重要です。

4. サービス通知トークンの再利用

同じLIFFアクセストークンでissueServiceNotificationTokenを呼べるのは1回だけです。複数回メッセージを送信する場合は、レスポンスに含まれるnotificationTokenを再利用する必要があります。

5. LINEミニアプリ上でのアクションが必要

サービスメッセージは、ユーザーがLINEミニアプリ上で行った操作に対してのみ送信できます。バッチ処理や外部システムからの一方的な通知には使用できません。必ずユーザーのアクション(予約ボタンのクリック、会員登録の完了など)を起点とする必要があります。

6. 禁止事項

値下げやプロモーション情報の送信は禁止されています。あくまでユーザーの操作に対する確認や応答としてのみ使用できます。

おわりに

今回は、LINEミニアプリのサービスメッセージの実装方法を紹介しました。メッセージングAPIと比較して、無料で利用できる点は大きなメリットですが、テンプレート形式のみで送信回数に制限があるなど、用途に応じて使い分ける必要があります。

サービスメッセージは、予約完了通知やリマインド、会員登録完了のお知らせなど、ユーザーにとって重要な情報を伝える場面で活用できます。LINEミニアプリを開発する際はぜひ検討してみてください。

以上。どなたかの参考になれば幸いです。

参考

https://developers.line.biz/ja/docs/line-mini-app/develop/service-messages/
https://developers.line.biz/ja/reference/line-mini-app/#issue-notification-token
https://developers.line.biz/ja/reference/line-mini-app/#send-service-message
https://developers.line.biz/ja/reference/messaging-api/#issue-stateless-channel-access-token

この記事をシェアする

FacebookHatena blogX

関連記事