How to build newsletter subscriber system on Laravel 10

How to build newsletter subscriber system on Laravel 10

Posted on:September 5, 2023 at 05:00 PM

Newsletter section is one of the important parts to interact with visitor. In todays article, I will show you how to build a newsletter system on laravel 10.

What you need?

Table of Content

Scenario

This is my plan:

  • User can able to submit email to /subscribe route. - Upon submission, a job will be dispatch to send email
  • In the email, user is able to verify email to /subscribe/<<hash>> route.
  • Send an welcome email.

Write Code

Let’s write code now.

Create table, model, settings, and migration

I want to create table for subscribers and model with Subscriber.

php artisan make:model Subscriber -m

It will create migration and model file together.

Update your table database/migrations/{***********}_create_subscribers_table.php

    ... 

    public function up(): void
    {
        Schema::create('subscribers', function (Blueprint $table) {
            $table->id();
            $table->string('email');
            $table->string('hash')->nullable();
            $table->timestamp('verified_at')->nullable();
            $table->timestamps();
        });
    }

Make your model Subscriber.php guarded nothing.

class Subscriber extends Model
{
    use HasFactory;

+    protected $fillable = [
+        'email',
+        'hash',
+        'verified_at',
+    ];
}

Setup your database

In .env file:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=<<DB_NAME>>
DB_USERNAME=<<DB_USER_NAME>>
DB_PASSWORD=<<DB_PASSWORD>>

Run migration In the terminal, run this:

php artisan migrate

Route

We need at least two routes to achieve our goal.

route/web.php

use App\Http\Controllers\SubscribeController;

Route::post('/subscribe', [SubscribeController::class, 'post']);
Route::get('/subscribe/{hash}', [SubscribeController::class, 'verify'])->name('subscribe.confirm');

Write Controller

In the controller, we need write logic now.

Create a form request

php artisan make:request NewsletterRequest 

Update your App\Http\Requests\NewsletterRequest

class NewsletterRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'email' => 'email|unique:email|min:6|max:64'
        ];
    }
}

Create & Update your controller now

php artisan make:controller SubscribeController 

Now, update the controller.

class SubscribeController extends Controller
{
    public function post(NewsletterRequest $request)
    {
        $validated = $request->validated();

        // Maybe you need more validation rules???
        $Subscriber = Subscriber::create([
            'email' => $validated['email'],
            'hash' => md5($validated['email']),
        ]);

        SubscriberJoinJob::dispatch($Subscriber);

        return redirect()->back()->with('success', 'You have successfully subscribed. Please check your email spam folder.');
    }

    public function verify(string $hash)
    {
        $subscriber = Subscriber::where('hash', $hash)->firstOrFail();

        $subscriber->update([
            'hash' => null,
            'verified_at' => now()
        ]);

        return redirect('/')
            ->with('success', 'You have successfully verified your email.');
    }
}

ℹ️ Feel free to update your code/logic.

Create job

You need to create a job:

php artisan make:job SubscriberJoinJob

Now, update your job App\Jobs\SubscriberJoinJob:

class SubscriberJoinJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public function __construct(public Subscriber $subscriber)
    {
    }

    public function handle(): void
    {
        Mail::to($this->subscriber->email)
            ->send(new UserJoin($this->subscriber));
    }
}

Crate a mailable class

Run this command to create a mailable class:

php artisan make:mail UserJoin

Update the mailable class App\Mail\UserJoin:

class UserJoin extends Mailable
{
    use Queueable, SerializesModels;

    public function __construct(public Subscriber $user)
    {
    }

    /**
     * Get the message envelope.
     */
    public function envelope(): Envelope
    {
        return new Envelope(
            subject: 'User Join',
        );
    }

    /**
     * Get the message content definition.
     */
    public function content(): Content
    {
        return new Content(
            view: 'subscriber',
        );
    }

    /**
     * Get the attachments for the message.
     *
     * @return array<int, \Illuminate\Mail\Mailables\Attachment>
     */
    public function attachments(): array
    {
        return [];
    }
}

Check more about mail: https://laravel.com/docs/10.x/mail#sending-mail

Update Mail Setup

As I mentioned that, I will use mailtrap for testing email, so you need to setup in the .env file.

MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=2525
MAIL_USERNAME= # value from mailtrap
MAIL_PASSWORD= # value from mailtrap
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS= # set the sender email address
MAIL_FROM_NAME="${APP_NAME}"

ℹ️ Check more details how to setup

Setup Queue

ℹ️ You don’t have to do anything if you don’t want to queue the email.

If you want to queue the email, then you need to setup the config, migrate the db, and start the queue worker.

Migrate Database Run the following command:

php artisan queue:table
 
php artisan migrate

Ref: https://laravel.com/docs/10.x/queues#driver-prerequisites

Setup the config Open .env file, and update QUEUE_CONNECTION value.

QUEUE_CONNECTION=database

Ref: https://laravel.com/docs/10.x/queues#driver-prerequisites

Start the Queue Worker Since your job is in the queue, someone needs to process it. Therefore you need a queue worker who will process the task. To process the task, simply run this command in the terminal and keep it open.

php artisan queue:work

Ref: https://laravel.com/docs/10.x/queues#the-queue-work-command

▶ Check the source file 🔗

Hope it will help you to solve the particular issue.

Thanks for reading 🙂.