始めに
完全未経験からwebエンジニア転職を目指して、Ruby on Railsを中心に学習している者です。
今回、WEBアプリでLINE Message APIを使用したLINE通知の実装方法を復習も兼ねてまとめていきたいと思います!
環境
- Mac OS
- Rails 7.2.3"
- ruby-3.3.6
- デプロイ先:Render
※ LINE Developersへの登録とプロバイダー設定は済んでいるものとします。
LINEログイン実装の記事でLINE Developersへの登録など詳しく書いてあるのでよろしければご覧ください↓
LINE Message APIとは
Messaging APIは、あなたのサービスとLINEユーザーの双方向コミュニケーションを可能にする機能です。
あなたのアカウントの友だちになってくれたユーザーにメッセージを送信できます。
また、グループに追加されていれば、グループ内でメッセージを送信することも可能です。
要するに、美容室や飲食店でよくある、公式LINEを作成して色々できるよってことですね!
webアプリの課題である通知機能。
メールだと埋もれがちですが、LINEなら開封率も高く、ユーザー体験(UX)の向上に直結します。
さらにLINEログインを実装している場合は相性も抜群です!
⚠️ 無料プランは(月)200通のみです。
詳しいプランはこちらを参照してください。
今回のゴールと実装の流れ
今回のゴール
今回は、ユーザーが設定した日時に、リマインドをLINEに届ける機能を実装します。
こんな感じでユーザーが日時を設定↓
実際の流れ
- 1, LINE公式アカウント作成(チャネルアクセストークン取得)
- 2, 必要gem導入&cron設定
- 3, 通知ロジックの実装(Rakeタスクの作成)
- 4, Renderでのcron job設定(重要)
になります。
⚠️ターミナルとシェル
実装する前に、一つ注意点があります。
初学者の人が勘違いしやすい点として、ターミナルとシェルの違いです。
ターミナル:Mac本体で動かす、開発環境(ローカル)で動かす場所
シェル:デプロイ先の本番環境で動かす場所
となっています。
開発環境では動いていたのに、本番環境では動かない、、といった場合はデプロイ先のシェル(今回だとRender)でコマンドを叩きましょう。
1. LINE公式アカウント作成(チャネルアクセストークン取得)
まずは通知送信用の公式アカウントを作成していきます。
公式ドキュメントがかなりわかりやすいです↓
ボットが作成できたら、Message API設定からチャネルトークンを取得し、config/credentials.yml.encや.envへ格納します。(今回は通知のみなのでチャネルトークンのみでいいですが、Webhook(返信機能)を使用する場合はシークレットキーも同様に格納してください)
line:
messaging_secret: "xxxxxx"
messaging_token: "yyyyyy"
credentials.yml.encの設定はこんな感じ!
LINE側の設定は以上です。
作成した公式LINEのアイコン等の設定は、Line official account managerのビジネスプロフィールページ設定から変更してみてください!
2. 必要gem導入&cron設定
ここからはRails側での設定になります。
gem導入
gem "line-bot-api"
gem "whenever", require: false
この2つをインストールします。
require: falseにしているのは、起動時にwheneverは常に使用する意味がないので、起動時のメモリ消費を抑えるためにつけています。
cron使用の設定
cronを叩く中身の前に、compose.ymlファイルなどの設定を見直していきます。
下記の記述がない場合追記しましょう。
# compose.yml
services:
~~略~~
web:
~~略~~
cron:
build:
context: .
dockerfile: Dockerfile.dev
command: bash -c "bundle install && bundle exec whenever --update-crontab && cron -f"
volumes:
- .:/myapp
- bundle_data:/usr/local/bundle:cached
- node_modules:/myapp/node_modules
environment:
- RAILS_ENV=development
- TZ=Asia/Tokyo
- BUNDLE_PATH=/usr/local/bundle
env_file:
- .env
depends_on:
db:
condition: service_healthy
# Dockerfile.dev
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs yarn vim cron
cronを使用できる環境を整えます。
通知ロジックの実装(Rakeタスクの作成)
通知機能の核となるRakeタスクの作成に移ります。
notification_setting.rbテーブル
主なカラム
- enabled
- 通知のON/OFF切り替え
- send_time
- 時間設定
- mon~sun
- 曜日設定
作成ファイル一覧
※notification_settingという通知設定テーブルを想定しています。
ご自身の命名に変えて作成してください。
- app/models/notification_setting.rb
- バリデーション設定
- app/controllers/notification_settings_controller.rb
- リダイレクトやCRUD設定
- app/views/notification_settings/edit.html.erb
- 通知機能画面の表示
- app/services/line_bot_service.rb
- LINE APIを叩く処理設定
- lib/tasks/notification.rake(重要)
- Cron Jobから呼び出されるコマンド設定。ここで通知の処理やメッセージを設定
- config/schedule.rb
- whenever用のファイル。ローカルでの確認用で、cronの読み込み頻度を設定。本番環境(Render)では使用しません。
以上が作成ファイルになります。
その中身を下記に記述します。
app/models/notification_setting.rb
class NotificationSetting < ApplicationRecord
belongs_to :user
validates :send_time, presence: true, if: :enabled?
end
バリデーション設定と他テーブルとの関係性を記述しています。
app/controllers/notifications_controller.rb
class NotificationSettingsController < ApplicationController
before_action :require_login
def edit
@setting = current_user.notification_setting || current_user.create_notification_setting
end
def update
@setting = current_user.notification_setting || current_user.build_notification_setting
p_hash = notification_params
if p_hash[:send_time].present?
hour, min = p_hash[:send_time].split(":")
@setting.send_time = Time.zone.now.change(hour: hour.to_i, min: min.to_i)
end
@setting.assign_attributes(p_hash.except(:send_time))
if @setting.save
redirect_to profile_path, notice: "通知設定を保存しました"
else
Rails.logger.error "保存失敗: #{@setting.errors.full_messages}"
flash.now[:alert] = "保存に失敗しました。入力内容を確認してください。"
render :edit, status: :unprocessable_entity
end
end
private
def notification_params
params.require(:notification_setting).permit(
:enabled, :send_time,
:mon, :tue, :wed, :thu, :fri, :sat, :sun
)
end
end
editの編集ページのみの設計になっています。
詳細ページなど必要な場合は追加してください。
app/views/notification_settings/edit.html.erb
<div class="max-w-md mx-auto p-6">
<% if current_user.line_user_id.present? %>
<h2 class="text-xl font-black mb-6 flex items-center gap-2">
<span class="w-2 h-6 bg-[#06C755] rounded-full"></span>
LINE通知設定
</h2>
<%= form_with model: @setting,
url: notification_setting_path,
method: @setting.persisted? ? :patch : :post,
data: { turbo: false } do |f| %>
<div class="mb-6 p-4 bg-stone-50 rounded-2xl flex justify-between items-center border border-stone-100">
<span class="font-bold text-stone-700">通知を有効にする</span>
<%= f.check_box :enabled, class: "w-6 h-6 rounded border-stone-300 text-[#06C755] focus:ring-[#06C755]" %>
</div>
<div class="mb-6">
<p class="text-sm font-bold text-stone-400 mb-3 uppercase tracking-wider">通知日時</p>
<div class="grid grid-cols-7 gap-2">
<% [:mon, :tue, :wed, :thu, :fri, :sat, :sun].each do |day| %>
<label class="cursor-pointer">
<%= f.check_box day, class: "hidden peer" %>
<div class="py-3 text-center border-2 border-stone-100 rounded-xl font-bold text-stone-400 transition-all peer-checked:border-[#06C755] peer-checked:bg-[#06C755]/5 peer-checked:text-[#06C755]">
<%= t("date.abbr_day_names")[[:sun, :mon, :tue, :wed, :thu, :fri, :sat].index(day)] %>
</div>
</label>
<% end %>
</div>
</div>
<div class="mb-8">
<p class="text-sm font-bold text-stone-400 mb-3 uppercase tracking-wider">配信時間</p>
<%= f.time_field :send_time, class: "w-full p-4 bg-stone-50 border-2 border-stone-100 rounded-2xl font-bold text-stone-700 focus:border-orange-200 outline-none transition-all" %>
</div>
<%= f.submit "設定を保存する", class: "w-full py-4 bg-gradient-to-r from-orange-400 to-orange-500 text-white font-black rounded-2xl shadow-lg shadow-orange-100 hover:scale-[1.02] transition-all active:scale-[0.98] cursor-pointer" %>
<% end %>
<% else %>
<div class="text-center py-10 bg-white border-2 border-dashed border-stone-200 rounded-[2.5rem] px-6">
<div class="bg-[#06C755]/10 w-20 h-20 rounded-full flex items-center justify-center mx-auto mb-6">
<svg class="w-10 h-10 text-[#06C755]" fill="currentColor" viewBox="0 0 24 24">
<path d="M24 10.304c0-5.369-5.383-9.738-12-9.738-6.616 0-12 4.369-12 9.738 0 4.814 4.269 8.846 10.036 9.608.391.084.922.258 1.057.592.121.303.079.778.039 1.085l-.171 1.027c-.053.303-.242 1.185 1.039.646 1.281-.54 6.913-4.073 9.448-6.972 1.747-2.008 2.551-3.654 2.551-5.286z"/>
</svg>
</div>
<h2 class="text-xl font-black text-stone-800 mb-3">LINE通知を使いませんか?</h2>
<p class="text-sm text-stone-500 mb-8 leading-relaxed">
LINEと連携すると、設定した時間に<br>
<span class="font-bold text-stone-700">「今日の献立どうする?」</span><br>
とお知らせが届くようになります。
</p>
<%= button_to "/auth/line",
method: :post,
data: { turbo: false },
class: "w-full py-4 bg-[#06C755] text-white font-black rounded-2xl shadow-lg shadow-green-100 hover:scale-[1.02] transition-all active:scale-[0.98] flex items-center justify-center gap-2" do %>
<span>LINEと連携して通知を受け取る</span>
<% end %>
</div>
<% end %>
</div>
ここはご自身の通知メッセージやレイアウトに変えてみてください!
app/services/line_bot_service.rb
require "line/bot"
class LineBotService
def initialize
@client ||= ::Line::Bot::Client.new { |config|
config.channel_secret = Rails.application.credentials.line[:messaging_secret]
config.channel_token = Rails.application.credentials.line[:messaging_token]
}
end
def send_message(line_user_id, message)
return if line_user_id.blank?
response = @client.push_message(line_user_id, message)
response.code
end
end
require "line/bot"で読み込みをしています。
config/credentials.yml.encでのキーの受け渡しですが、ENVを使用している場合は下記のように変えてください。
config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
lib/tasks/notification.rake
require "line/bot"
require "line/bot/v2/messaging_api/api/messaging_api_client"
namespace :notification do
desc "LINEのリマインド通知を送信します"
task send_reminders: :environment do
now = Time.current.in_time_zone("Asia/Tokyo")
day_of_week = now.strftime("%a").downcase
puts "--- [Task Start] JST Time: #{now.strftime('%H:%M:%S')}, Day: #{day_of_week} ---"
all_configs = NotificationSetting.includes(:user).where(enabled: true).to_a
puts "DEBUG: Fetched #{all_configs.count} enabled settings from DB."
targets = all_configs.select do |setting|
next unless setting.send(day_of_week) == true
config_time = setting.send_time.in_time_zone("Asia/Tokyo").strftime("%H:%M")
current_time = now.strftime("%H:%M")
is_match = (config_time == current_time)
puts " SettingID:#{setting.id} | User:#{setting.user_id} | Config:#{config_time} | Now:#{current_time} -> Match: #{is_match}"
is_match
end
if targets.empty?
puts "--- [Finished] No matching targets for this minute. ---"
next
end
line_creds = Rails.application.credentials.line
if line_creds&.[](:messaging_token).nil?
puts "ERROR: Line messaging_token is missing!"
next
end
client = Line::Bot::V2::MessagingApi::ApiClient.new(channel_access_token: line_creds[:messaging_token])
targets.each do |setting|
line_user_id = setting.user&.line_user_id
next if line_user_id.blank?
action = Line::Bot::V2::MessagingApi::URIAction.new(label: "レシピを探す", uri: "https://jisui-save.onrender.com")
template = Line::Bot::V2::MessagingApi::ButtonsTemplate.new(text: "自炊の時間です!🍳", actions: [ action ])
message = Line::Bot::V2::MessagingApi::TemplateMessage.new(type: "template", alt_text: "自炊の時間です🍳", template: template)
push_request = Line::Bot::V2::MessagingApi::PushMessageRequest.new(to: line_user_id, messages: [ message ])
begin
client.push_message(push_message_request: push_request)
puts " SUCCESS: Sent to User ID #{setting.user_id}"
rescue => e
puts " FAILED: User ID #{setting.user_id} - #{e.message}"
end
end
puts "--- [Task End] ---"
end
end
エラーが多かったためデバックコードがありますが、不必要なら削除してください。
config_time = setting.send_time.in_time_zone("Asia/Tokyo").strftime("%H:%M")
current_time = now.strftime("%H:%M")
ここで、ローカルとデプロイ先での時間のずれを東京時間に統一しています。
config/schedule.rb
set :output, "/log/cron.log"
set :environment, :development
every 1.minute do
rake "notification:send_reminders"
end
本番環境ではRenderのcron jobを使用するため、最低限の記述になります。
every 1.minute do
rake "notification:send_reminders"
で1分単位でcronを動かしています。
ファイル作成したら、ローカルで通知が来るか試してみましょう!
通知が来ない場合は、、
ログでエラーの確認をしましょう。
下記のコマンドで確認
tail -n 10 log/development.log
通知自体設定されているかは、docker compose exec web rails cでRailsコンソールに入り、下記のコマンドを叩いてください。
User.all.order(:id).map do |u|
s = u.notification_setting
status = s.present? ? "✅ 設定あり(ID:#{s.id})" : "❌ 設定なし"
time = s&.send_time ? s.send_time.strftime('%H:%M') : "--:--"
"UserID:#{u.id.to_s.ljust(3)} | #{status.ljust(12)} | 時間:#{time}"
end.each { |line| puts line }; nil
UserID:1 | ✅ 設定あり(ID:6) | 時間:02:57
のようなIDと時間が入っていれば登録できています。
4. Renderでのcron job設定(重要)
RenderのDashboardの右上の+NEWからCron jobを選択。
SettingsのSchedule欄で* * * * *と設定。
Docker Commandにbundle exec rake notification:send_remindersと設定。
これで1分間隔でcronが起動して、通知設定があるか判断してくれます。
※bundle exec rake notification:send_remindersの部分はRakeタスクで設定している命名と合わせてください。
# lib/tasks/notification.rake
namespace :notification do
desc "LINEのリマインド通知を送信します"
task send_reminders: :environment do
環境変数にDATABASE_URLとRAILS_MASTER_KEYを追加します。
⚠️ENVを使用している場合は、.envに格納してあるチャネルトークンも追加してください!
Web Service側の環境変数にトークンを入れていても、Cron Job側にも同じ設定を入れないと、通知タスクは動きません。
全て設定できたら、一番下のデプロイボタンを押して完了です!
もしCron jobが失敗したら、、
Renderで「Status 1」が出たら
RenderのCron Jobが失敗すると Status 1 と表示されます。これは「何らかのエラーで止まった」という合図です。焦らずに 「Logs」 タブを確認しましょう。環境変数の設定漏れや、DB接続エラーなど、原因が必ずログに書き出されています。
最後に
今回は、LINE Message APIを使用したLINE通知機能を実装しました。
私自身、初めての実装だったので「ローカルでは動くのに本番環境では動かない、、」と悩むことがありました。
ただ、ログを見ると「Status 1(エラー)」とエラーがでて上に原因が載っているので、冷静に対応すれば必ず解決できると思います。
エラーで思考ロックすると地獄なのでこまめに休むことをお勧めします笑
ここまで読んでいただきありがとうございます!







