ccoeder's avatar

Adding multiple notification channels to the user model.

Hey,

I'm trying to build a system that allows user's to add multiple notification channels, such as they could add their accountant's email address and also their slack webhook and one more additional webhook url like their api or something. There is no limit.

So I've designed a table, named as user_notification_channels. When the user signs up, I add his/her email address to this table.

Schema::create('user_notification_channels', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedBigInteger('user_id');
            $table->string('name');
            $table->tinyInteger('type');
            $table->string('url');

            $table->timestamps();

            $table->foreign('user_id')
                ->references('id')->on('users')
                ->onDelete('cascade');
        });

And this is my code on the event listener that sends the notification to the user and his/her additional notification channels.

        $user = $event->model->user;
        $user->notificationChannels()->each(function ($item, $key) {
            $type = NotificationChannels::getDescription($item->type);

            Notification::route(strtolower($type), $item->url)->notify(new MyNotification());
        });

But it's not working properly, first of all, I don't want to iterate all of the notification channels that user has, is there a proper way to do it?

And second, what should I do for adding multiple webhook url's or slack url's?

Thanks for the help.

0 likes
10 replies
mikenewbuild's avatar

If your user potentially has one or more notification types then you will have to do that iteration somewhere.

Unless you have a unique constraint on user_id and type there's no reason why the user can't have multiple webhooks for the same service.

Unless I'm missing something, I don't see an issue with your approach as you've described it.

ccoeder's avatar

Thank you for your answer @mikenewbuild, what should I write here in the notification file?

     /**
     * Get the notification's delivery channels.
     *
     * @param  mixed  $notifiable
     * @return array
     */
    public function via($notifiable)
    {
        return ['mail'];
    }

Because I'm sending notifications one by one I think I have to change here every time, am I right?

For example, when I'm sending an email to anonymous mail address, it will also try to send it to slack web hook url. How can I manage that?

Actually this is what I want for all notification drivers: https://github.com/laravel-notification-channels/webhook/issues/9

willvincent's avatar

Your via() method would simply have to return an array of all the relevant channels configured for the user being notified, for any given notification event... no?

ccoeder's avatar

Yes but when I look to the logs (telescope) I see that it tries to send slack notification via mail driver and also slack driver.

So when the user has 3 notification channel, it logs like this.

Example records

id (pk)  | user_id | name | type    | url
1        | 1       | a    | email   | [email protected]
2        | 1       | b    | email   | [email protected]
3        | 1       | c    | slack   | hooks/slack/example

Example logs, happens at the same time

Recipient: Anonymous:[email protected]  channel => mail
Recipient: Anonymous:[email protected]  channel => slack

Recipient: Anonymous:[email protected]  channel => mail
Recipient: Anonymous:[email protected]  channel => slack

Recipient: Anonymous:hooks/slack/example  channel => mail
Recipient: Anonymous:hooks/slack/example  channel => slack

Is this something normal? Why it tries to send from every channel?

willvincent's avatar

Have been away from laravel for a few years, and don't have first-hand experience with notification channels... seems like a misconfiguration somewhere though as the docs pretty clearly indicate that you return an array of channels..

Probably a missing something somewhere that properly ushers things into the appropriate notification channel by defined type.

Probably relevant:

To route Slack notifications to the proper location, define a routeNotificationForSlack method on your notifiable entity.

https://laravel.com/docs/5.8/notifications#routing-slack-notifications

ccoeder's avatar

I've taken a look at routeNotificationForSlack function and it is only allowed to return a string but I need to return an array of slack webhook url's. This is why I'm using on-demand notifications.

mikenewbuild's avatar

This is the reason why I think you will have to iterate over your channels and create a notification for each one.

This seems desirable to me, as it allows individual notifications to fail independently.

If you're looking to clean your code up a little you could implement polymorphism in your foreach loop.

ccoeder's avatar

@mikenewbuild yes, I was trying to implement polymorphism :) what do you think about the telescope logs, do you think is normal?

mikenewbuild's avatar
Level 22

Yes, that's what I would expect from the docs. You can send one notice to multiple channels, not multiple notices to one channel in a single Notification (if I have understood correctly).

I think the way you have tried to write it would work more like:


$notification = new Notification;
$user->notificationChannels()->each(function ($item, $key) {
    $type = NotificationChannels::getDescription($item->type);
    $notification::route(strtolower($type), $item->url);
});

$notification->notify(new MyNotification());

But you might be limited to one route of each type.

This may not be the best way (!!!) but just off the top of my head I might try to implement a notify method on the NotificationChannels model itself, which is essentially a factory for the type of notification I want.

My (pseudo) implementation might look something like:


$user = $event->model->user;
$user->notificationChannels->each->notify(new MyNotification());

NotificationChannels.php


// etc

public function notify($notification)
{
    $this->getNotifiable()->notify($notification);
}

protected function getNotifiable()
{
    if ($this->type === 'email') {
        // return the mail notification instance
    }

    if ($this->type === 'slack') {
        // return the slack notification instance
    }
}


ccoeder's avatar

Thank you @mikenewbuild , it's not perfect but it's working! I wonder what other developers might have done for solving this kind of problem.

Please or to participate in this conversation.