九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、商品を販売する際に
気をつけておかないといけないのが、
顧客満足度
ですよね。
最近は誰でもスマホを持っているので、
レビューで★1つだけ、なんてことになると
後続の販売が難しくなるわけです。
しかし、そう考えると教育業界は
難しいものがあります。
なぜなら、いくら良いものを販売していても、
努力せずに悪いレビューだけ書く
という人が一定数出てくるからです。
つまり、「優良とは言えない顧客」ですね。
マーケティングでは、こういった顧客を
事前に除外(スクリーニング)しておくテクニックがあります。
例えば、
- 何回目か以降のメルマガだけでセールスする
- いくつか動画を送り、すべて視聴した人だけにセールスする
- メルマガの最後に質問フォームを用意しておき、全部送信した人にセールスする
つまり、ある程度の努力ができる人だけに販売する
というテクニックです。
※ ちなみに、有名マーケターの イングリッシュおさる さんは、さらに個別面談までする徹底ぶりです!
そこで❗
今回はこの「見込み客のスクリーニング」をLaravel
で作ってみることにしました。
ぜひ何かの参考になりましたら嬉しいです。😊✨
「迫佑樹 社長の
(PR) Twitterマーケティングマスター講座」
(59,800円)購入!ガチで良いです(感謝😊)
開発環境: Laravel 11.x
目次
やりたいこと
今回は、以下の手順でスクリーニングしてみます。
- LINE メッセージで記事のURLを送る
- 記事の最後に質問フォームを用意
- 送信してくれたらそれを保存
そして、3回全てに回答してくれていたら
商品のURL
を送信してセールスする
という流れです。
ただし、LINE
を含めると複雑になりすぎてしまうので、
実際にLINEへ送信はせず、メール送信で代替します。
では、楽しんでやっていきましょう❗
DB 周りをつくる
今回はメインではない部分なので
サクッといきます。
以下のコマンドを実行してください。
php artisan make:model LineUser -ms php artisan make:model LineNewsletter -ms php artisan make:model LineUserNewsletter -m
そして、作成したファイルを以下のように変更します。
app/Models/LineUser.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class LineUser extends Model { use HasFactory; // Relationship public function newsletters() { return $this->belongsToMany(Newsletter::class, 'line_user_newsletters', 'line_user_id', 'newsletter_id'); } }
app/Models/Newsletter.php
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Newsletter extends Model { use HasFactory; // Relationship public function line_user() { return $this->belongsToMany(LineUser::class, 'line_user_newsletters', 'newsletter_id', 'line_user_id'); } // Others public static function getTestNewsletterIds() { // TODO: テスト用。本来ここは DB から取得するべきです return [1, 2, 3]; // 送信するメルマガのID(この順番に送信される) } }
database/migrations/****_**_**_******_create_line_users_table.php
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { /** * Run the migrations. */ public function up(): void { Schema::create('line_users', function (Blueprint $table) { $table->id(); $table->uuid('uuid')->unique()->comment('UUID'); $table->string('line_id')->unique()->comment('LINEのID'); $table->string('mode')->comment('チャネルの状態'); $table->string('display_name')->comment('LINEの名前'); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('line_users'); } };
database/migrations/****_**_**_******_create_newsletters_table.php
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { /** * Run the migrations. */ public function up(): void { Schema::create('newsletters', function (Blueprint $table) { $table->id(); $table->uuid('uuid')->unique()->comment('UUID'); $table->string('line_body')->comment('LINEの本文'); $table->string('web_body')->comment('ウェブページの本文'); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('newsletters'); } };
database/migrations/****_**_**_******_create_line_user_newsletters_table.php
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { /** * Run the migrations. */ public function up(): void { Schema::create('line_user_newsletters', function (Blueprint $table) { $table->id(); $table->unsignedBigInteger('line_user_id')->comment('LINEユーザーID'); $table->unsignedBigInteger('newsletter_id')->comment('メルマガID'); $table->text('answer')->nullable()->comment('回答'); $table->dateTime('answered_at')->nullable()->comment('回答日時'); $table->timestamps(); $table->foreign('line_user_id')->references('id')->on('line_users'); $table->foreign('newsletter_id')->references('id')->on('newsletters'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('line_user_newsletters'); } };
database/seeders/LineUserSeeder.php
<?php namespace Database\Seeders; use App\Models\LineUser; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use Illuminate\Support\Str; class LineUserSeeder extends Seeder { /** * Run the database seeds. */ public function run(): void { for($i = 1; $i <= 10; $i++) { $line_user = new LineUser(); $line_user->uuid = Str::uuid(); $line_user->line_id = Str::random(); $line_user->mode = 'active'; $line_user->display_name = 'ユーザー' . $i; $line_user->save(); } } }
database/seeders/NewsletterSeeder.php
<?php namespace Database\Seeders; use App\Models\Newsletter; use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; use Illuminate\Support\Str; class NewsletterSeeder extends Seeder { /** * Run the database seeds. */ public function run(): void { for($i = 1; $i <= 3; $i++) { $newsletter = new Newsletter(); $newsletter->uuid = Str::uuid(); $newsletter->line_body = $i .'つ目のメルマガの内容(LINE)'; $newsletter->web_body = $i .'つ目のメルマガの内容(Web)' ." \n質問に答えてね\n↓↓↓"; $newsletter->save(); } } }
そして、Seeder
をLaravel
へセット。
database/seeders/DatabaseSeeder.php
<?php namespace Database\Seeders; use App\Models\User; // use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; class DatabaseSeeder extends Seeder { /** * Seed the application's database. */ public function run(): void { $this->call([ LineUserSeeder::class, NewsletterSeeder::class, ]); } }
では、DB
の初期化します。
php artisan migrate:fresh --seed
すると実際のテーブルはこうなりました。
メール送信部分をつくる
今回は「LINEの代替」としてMailable
をつくります。
以下のコマンドを実行して下さい。
php artisan make:mail SendNewsletter
すると、ファイルが作成されるので中身を変更します。
app/Mail/SendNewsletter.php
<?php namespace App\Mail; use App\Models\LineUser; use App\Models\Newsletter; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Mail\Mailable; use Illuminate\Mail\Mailables\Content; use Illuminate\Mail\Mailables\Envelope; use Illuminate\Queue\SerializesModels; class SendNewsletter extends Mailable { use Queueable, SerializesModels; /** * Create a new message instance. */ public function __construct(private Newsletter $newsletter, private LineUser $line_user) {} /** * Get the message envelope. */ public function envelope(): Envelope { return new Envelope( subject: '【 Console dot Log メルマガ 】', ); } /** * Get the message content definition. */ public function content(): Content { return new Content( view: 'emails.newsletter', with: [ 'newsletter' => $this->newsletter, 'line_user' => $this->line_user, ] ); } /** * Get the attachments for the message. * * @return array<int, \Illuminate\Mail\Mailables\Attachment> */ public function attachments(): array { return []; } }
では、Mailable
にセットしたビューをつくります。
php artisan make:view emails.newsletter
ファイルの中身はこうなります。
resources/views/emails/newsletter.blade.php
{{ $newsletter->line_body }}<br><br> <a href="{{ route('newsletter.show', [$newsletter->uuid, $line_user->uuid]) }}">本文はこちら</a>
コントローラーをつくる
では、(受信したメールにあるURL
をクリックしたときの)
メルマガ本文を表示するためにコントローラーをつくります。
以下のコマンドを実行して下さい。
php artisan make:controller NewsletterController
中身をこうします。
app/Http/Controllers/NewsletterController.php
<?php namespace App\Http\Controllers; use App\Models\LineUser; use App\Models\LineUserNewsletter; use App\Models\Newsletter; use Illuminate\Http\Request; class NewsletterController extends Controller { public function show($newsletter_uuid, $line_user_uuid) { $newsletter = Newsletter::where('uuid', $newsletter_uuid)->first(); $line_user = LineUser::where('uuid', $line_user_uuid)->first(); return view('newsletters.show', [ 'newsletter' => $newsletter, 'line_user' => $line_user, ]); } public function storeAnswer(Request $request) { // 注意: バリデーションは省略しています $line_user = LineUser::where('uuid', $request->line_user_uuid)->first(); $newsletter = Newsletter::where('uuid', $request->newsletter_uuid)->first(); $line_user_newsletter = LineUserNewsletter::query() ->where('line_user_id', $line_user->id) ->where('newsletter_id', $newsletter->id) ->first(); $line_user_newsletter->answer = $request->answer; $line_user_newsletter->answered_at = now(); $line_user_newsletter->save(); $newsletter_ids = Newsletter::getTestNewsletterIds(); $answer_count = LineUserNewsletter::query() ->whereIn('newsletter_id', $newsletter_ids) ->where('line_user_id', $line_user->id) ->whereNotNull('answer') ->count(); if ($answer_count === count($newsletter_ids)) { // 全ての質問に回答したら return 'この人は優良な顧客である可能性が高いので、ここでセールスメールを送る!'; } return '回答を受け付けました!'; } }
ビューをつくる
次に先ほどのコントローラー内でセットしたビューをつくります。
php artisan make:view newsletters.show
作成されたファイルはこうします。
resources/views/newsletters/show.blade.php
<html> <head> <title>メルマガ</title> </head> <body> <p style="white-space:pre-wrap;">{{ $newsletter->web_body }}</p> <div> <form method="post" action="{{ route('newsletter.store_answer') }}"> @csrf <input type="hidden" name="newsletter_uuid" value="{{ $newsletter->uuid }}"> <input type="hidden" name="line_user_uuid" value="{{ $line_user->uuid }}"> <label> <textarea name="answer" rows="4" cols="40"></textarea> </label> <br><br> <button type="submit">送信</button> </form> </div> </body> </html>
Artisan コマンドをつくる
続いて、LINE
(今回はメール)送信する
Artisan
コマンドをつくります。
php artisan make:command NewsletterCommand
そして、中身をこうします。
app/Console/Commands/NewsletterCommand.php
<?php namespace App\Console\Commands; use App\Mail\SendNewsletter; use App\Models\LineUser; use App\Models\Newsletter; use Illuminate\Console\Command; use Illuminate\Support\Facades\Mail; class NewsletterCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'send:newsletter'; /** * The console command description. * * @var string */ protected $description = 'Send newsletters to line users'; /** * Execute the console command. */ public function handle() { $newsletter_ids = Newsletter::getTestNewsletterIds(); $newsletters = Newsletter::query() ->whereIn('id', $newsletter_ids) ->get(); $sent_line_user_ids = []; foreach ($newsletter_ids as $newsletter_id) { $newsletter = $newsletters->firstWhere('id', $newsletter_id); $prev_newsletter_index = array_search($newsletter_id, $newsletter_ids) - 1; $prev_newsletter_id = data_get($newsletter_ids, $prev_newsletter_index, null); $Line_users = LineUser::query() ->whereDoesntHave('newsletters', function ($query) use ($newsletter) { // まだ送信していないユーザー $query->where('newsletter_id', $newsletter->id); }) ->whereNotIn('id', $sent_line_user_ids) // 一度の複数の送信はしない ->when($prev_newsletter_id, function($query, $prev_newsletter_id) { // 前回のメルマガに回答してるユーザーだけ $query->whereHas('newsletters', function($q) use($prev_newsletter_id) { $q->where('newsletter_id', $prev_newsletter_id) ->whereNotNull('answer'); }); }) ->get(); foreach ($Line_users as $Line_user) { $Line_user->newsletters()->attach($newsletter->id, [ 'created_at' => now(), 'updated_at' => now(), ]); $sent_line_user_ids[] = $Line_user->id; // 本来はここで LINE にメッセージを送信する処理が入る Mail::to('test@example.com')->send(new SendNewsletter($newsletter, $Line_user)); $this->info($Line_user->display_name .' にメルマガ(ID:'. $newsletter->id .')を送信'); } } } }
ルートをつくる
では、最後にルートです。
routes/web.php
<?php use Illuminate\Support\Facades\Route; use App\Http\Controllers\NewsletterController; Route::prefix('newsletter')->controller(NewsletterController::class)->group(function () { Route::get('{newsletter_uuid}/{line_user_uuid}', 'show')->name('newsletter.show'); Route::post('answer', 'storeAnswer')->name('newsletter.store_answer'); });
テストしてみる
では、まずは作成したArtisan
コマンドを実行してみましょう。
php artisan send:newsletter
すると・・・・・・
はい❗
メルマガが(最初は全員に)送信されました。
では、「ID:1」の人に送ったメッセージを見てみましょう。
うまくメッセージが届いていますね。
では、リンクをクリックしてみます。
どうなるでしょうか・・・・・・
はい❗
メルマガの内容とフォームが表示されました。
ここに入力して送信すると
「ちゃんと回答をしたユーザー」として次のメルマガも送信します。
では以下のようにして入力してみましょう。
すると・・・・・・
はい❗
保存が完了しました。
ではデータベースの方も確認しておきましょう。
はい❗
テーブルに回答とその日時が保存されています。
成功です😊✨
では、先ほどのArtisan
コマンドを実行して2通目のメルマガも送信してみましょう。
すると・・・・・・
はい❗
今回は、「前回のメルマガで回答をした」ID:1の人だけに
送信されていますね。
では、これを繰り返して3通目のメルマガにある
回答フォームを送信してみます。
うまくいくでしょうか・・・・・・
はい❗
3通のメルマガを読み、フォーム送信したので、
「優良な顧客」と判断され、メッセージが変化しました。
すべて成功です😊✨
企業様へのご提案
冒頭に書いたとおり「見込み客は集めれば集めるほどいい」
というわけではない時代になってきているかと思います。
そして、顧客満足度を最大限に高めることは、
以下のような流れにつながります。
- 高評価につながり口コミになる(集客の増加)
- 満足しているので、他の商品も購入してもらえる(成約率の増加、クロスセル、LTVの増加)
- 同じ理由から、より高いバージョンの商品が購入される(アップセル、LTVの増加)
- 炎上の可能性は低くなる(リスクの軽減)
もしこういったマーケティングを実施するシステムをご希望でしたら、
ぜひお問い合わせからご相談下さい。
お待ちしております。😊
おわりに
ということで、今回は「見込み客をスクリーニングする」機能
を実装してみました。
ちなみに、マーケティングを学べば学ぶほど、
知らないことや感心することがいっぱいです。
となると、知識がつくので逆に悪質な売り方をしていると
すぐに気づくことができるという話を聞いてナルホド納得でした。
やはり何でも知識がないのはリスク、ということでしょうか。
特にエライ目にあったことがあるわけじゃないですが、
こういうのは、もっと早く勉強しておくべきだったと感じる今日この頃です。
そして、今回Twitter
運用に関する「59,800円 の教材」を
買ってみましたが、将来の成果を考えると、
安いと言わざるを得ませんでした。
今年は、知識投資への「課金元年」になりそうです。
ぜひ皆さんも時間を短縮して、知識を獲得してみてくださいね。
ではでは〜❗
「こんな高い教材買うの
マジでビビッた…でも、
何倍も回収できそうなんですよね👍」