Currently, we support these notification delivery methods out of the box:
- Database
- ActionCable channels
- Slack
- Microsoft Teams
- Twilio (SMS)
- Vonage / Nexmo (SMS)
And you can easily add new notification types for any other delivery methods.
Run the following command to add Noticed to your Gemfile
bundle add "noticed"
To save notifications to your database, use the following command to generate a Notification model.
rails generate noticed:model
This will generate a Notification model and instructions for associating User models with the notifications table.
To generate a notification object, simply run:
rails generate noticed:notification CommentNotification
To send a notification to a user:
# Instantiate a new notification
notification = CommentNotification.with(comment: @comment)
# Deliver notification in background job
notification.deliver_later(@comment.post.author)
# Deliver notification immediately
notification.deliver(@comment.post.author)
# Deliver notification to multiple recipients
notification.deliver_later(User.all)
This will instantiate a new notification with the comment
stored in the notification's params.
Each delivery method is able to transform this metadata that's best for the format. For example, the database may simply store the comment so it can be linked when rendering in the navbar. The websocket mechanism may transform this into a browser notification or insert it into the navbar.
Notifications inherit from Noticed::Base
. This provides all their functionality and allows them to be delivered.
To add delivery methods, simply include
the module for the delivery methods you would like to use.
class CommentNotification < Noticed::Base
deliver_by :database
deliver_by :action_cable
deliver_by :email, mailer: 'CommentMailer', if: :email_notifications?
# I18n helpers
def message
t(".message")
end
# URL helpers are accessible in notifications
# Don't forget to set your default_url_options so Rails knows how to generate urls
def url
post_path(params[:post])
end
def email_notifications?
!!recipient.preferences[:email]
end
after_deliver do
# Anything you want
end
end
Shared Options
if: :method_name
- Callsmethod_name
and cancels delivery method iffalse
is returnedunless: :method_name
- Callsmethod_name
and cancels delivery method iftrue
is returneddelay: ActiveSupport::Duration
- Delays the delivery for the given duration of time
You can define helper methods inside your Notification object to make it easier to render.
Rails url helpers are included in notification classes by default so you have full access to them just like you would in your controllers and views.
Don't forget, you'll need to configure default_url_options
in order for Rails to know what host and port to use when generating URLs.
Rails.application.routes.default_url_options[:host] = 'localhost:3000'
Callbacks
Like ActiveRecord, notifications have several different types of callbacks.
class CommentNotification < Noticed::Base
deliver_by :database
deliver_by :email, mailer: 'CommentMailer'
# Callbacks for the entire delivery
before_deliver :whatever
around_deliver :whatever
after_deliver :whatever
# Callbacks for each delivery method
before_database :whatever
around_database :whatever
after_database :whatever
before_email :whatever
around_email :whatever
after_email :whatever
end
When using deliver_later
callbacks will be run around queuing the delivery method jobs (not inside the jobs as they actually execute).
Defining custom delivery methods allows you to add callbacks that run inside the background job as each individual delivery is executed. See the Custom Delivery Methods section for more information.
We've added translate
and t
helpers like Rails has to provide an easy way of scoping translations. If the key starts with a period, it will automatically scope the key under notifications
and the underscored name of the notification class it is used in.
For example:
t(".message")
looks up en.notifications.new_comment.message
You can use the if:
and unless:
options on your delivery methods to check the user's preferences and skip processing if they have disabled that type of notification.
For example:
class CommentNotification < Noticed::Base
deliver_by :email, mailer: 'CommentMailer', if: :email_notifications?
def email_notifications?
recipient.email_notifications?
end
end
The delivery methods are designed to be modular so you can customize the way each type gets delivered.
For example, emails will require a subject, body, and email address while an SMS requires a phone number and simple message. You can define the formats for each of these in your Notification and the delivery method will handle the processing of it.
Writes notification to the database.
deliver_by :database
Note: Database notifications are special in that they will run before the other delivery methods. We do this so you can reference the database record ID in other delivery methods. For that same reason, the delivery can't be delayed (via the delay
option) or an error will be raised.
-
association
- OptionalThe name of the database association to use. Defaults to
:notifications
-
format: :format_for_database
- OptionalUse a custom method to define the attributes saved to the database
Sends an email notification. Emails will always be sent with deliver_later
deliver_by :email, mailer: "UserMailer"
-
mailer
- RequiredThe mailer that should send the email
-
method: :invoice_paid
- OptionalUsed to customize the method on the mailer that is called
-
format: :format_for_email
- OptionalUse a custom method to define the params sent to the mailer.
recipient
will be merged into the params.
Sends a notification to the browser via websockets (ActionCable channel by default).
deliver_by :action_cable
-
format: :format_for_action_cable
- OptionalUse a custom method to define the Hash sent through ActionCable
-
channel
- OptionalOverride the ActionCable channel used to send notifications.
Defaults to
Noticed::NotificationChannel
Sends a Slack notification via webhook.
deliver_by :slack
-
format: :format_for_slack
- OptionalUse a custom method to define the payload sent to Slack. Method should return a Hash.
-
url: :url_for_slack
- OptionalUse a custom method to retrieve the Slack Webhook URL. Method should return a String.
Defaults to
Rails.application.credentials.slack[:notification_url]
Sends a Teams notification via webhook.
deliver_by :microsoft_teams
-
format: :format_for_teams
- OptionalUse a custom method to define the payload sent to Microsoft Teams. Method should return a Hash. Documentation for posting via Webhooks available at: https://docs.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook
{ title: "This is the title for the card", text: "This is the body text for the card", sections: [{activityTitle: "Section Title", activityText: "Section Text"}], "potentialAction": [{ "@type": "OpenUri", name: "Button Text", targets: [{ os: "default", uri: "https://example.com/foo/action" }] }] }
-
url: :url_for_teams_channel
: - OptionalUse a custom method to retrieve the MS Teams Webhook URL. Method should return a string.
Defaults to
Rails.application.credentials.microsoft_teams[:notification_url]
Sends an SMS notification via Twilio.
deliver_by :twilio
-
credentials: :get_twilio_credentials
- OptionalUse a custom method to retrieve the credentials for Twilio. Method should return a Hash with
:account_sid
,:auth_token
and:phone_number
keys.Defaults to
Rails.application.credentials.twilio[:account_sid]
andRails.application.credentials.twilio[:auth_token]
-
url: :get_twilio_url
- OptionalUse a custom method to retrieve the Twilio URL. Method should return the Twilio API url as a string.
Defaults to
"https://api.twilio.com/2010-04-01/Accounts/#{twilio_credentials(recipient)[:account_sid]}/Messages.json"
-
format: :format_for_twilio
- OptionalUse a custom method to define the payload sent to Twilio. Method should return a Hash.
Defaults to:
{ Body: notification.params[:message], From: twilio_credentials[:number], To: recipient.phone_number }
Sends an SMS notification via Vonage / Nexmo.
deliver_by :vonage
-
credentials: :get_credentials
- OptionalUse a custom method for retrieving credentials. Method should return a Hash with
:api_key
and:api_secret
keys.Defaults to
Rails.application.credentials.vonage[:api_key]
andRails.application.credentials.vonage[:api_secret]
-
deliver_by :vonage, format: :format_for_vonage
- OptionalUse a custom method to generate the params sent to Vonage. Method should return a Hash. Defaults to:
{ api_key: vonage_credentials[:api_key], api_secret: vonage_credentials[:api_secret], from: notification.params[:from], text: notification.params[:body], to: notification.params[:to], type: "unicode" }
A common pattern is to deliver a notification via the database and then, after some time has passed, email the user if they have not yet read the notification. You can implement this functionality by combining multiple delivery methods, the delay
option, and the conditional if
/ unless
option.
class CommentNotification < Noticed::Base
deliver_by :database
deliver_by :email, mailer: 'CommentMailer', delay: 15.minutes, unless: :read?
end
Here a notification will be created immediately in the database (for display directly in your app). If the notification has not been read after 15 minutes, the email notification will be sent. If the notification has already been read in the app, the email will be skipped.
You can also configure multiple fallback options:
class CriticalSystemNotification < Noticed::Base
deliver_by :slack
deliver_by :email, mailer: 'CriticalSystemMailer', delay: 10.minutes, unless: :read?
deliver_by :twilio, delay: 20.minutes, unless: :read?
end
In this scenario, you can create an escalating notification that starts with a ping in Slack, then emails the team, and then finally sends an SMS to the on-call phone.
You can mix and match the options and delivery methods to suit your application specific needs.
To generate a custom delivery method, simply run
rails generate noticed:delivery_method Discord
This will generate a new DeliveryMethods::Discord
class inside the app/notifications/delivery_methods
folder, which can be used to deliver notifications to Discord.
class DeliveryMethods::Discord < Noticed::DeliveryMethods::Base
def deliver
# Logic for sending a Discord notification
end
end
You can use the custom delivery method thus created by adding a deliver_by
line with a unique name and class
option in your notification class.
class MyNotification < Noticed::Base
deliver_by :discord, class: "DeliveryMethods::Discord"
end
Delivery methods have access to the following methods and attributes:
notification
- The instance of the Notification. You can call methods on the notification to let the user easily override formatting and other functionality of the delivery method.options
- Any configuration options on thedeliver_by
line.recipient
- The object who should receive the notification. This is typically a User, Account, or other ActiveRecord model.params
- The params passed into the notification. This is details about the event that happened. For example, a user commenting on a post would have params of{ user: User.first }
The presence of the delivery method options is automatically validated if using the option(s)
method.
If you want to validate that the passed options contain valid values, or to add any custom validations, override the self.validate!(delivery_method_options)
method from the Noticed::DeliveryMethods::Base
class.
class DeliveryMethods::Discord < Noticed::DeliveryMethods::Base
option :username # Requires the username option to be passed
def deliver
# Logic for sending a Discord notification
end
def self.validate!(delivery_method_options)
super # Don't forget to call super, otherwise option presence won't be validated
# Custom validations
if delivery_method_options[:username].blank?
raise Noticed::ValidationError, 'the `username` option must be present'
end
end
end
class CommentNotification < Noticed::Base
deliver_by :discord, class: 'DeliveryMethods::Discord'
end
Now it will raise an error because a required argument is missing.
To fix the error, the argument has to be passed correctly. For example:
class CommentNotification < Noticed::Base
deliver_by :discord, class: 'DeliveryMethods::Discord', username: User.admin.username
end
Callbacks for delivery methods wrap the actual delivery of the notification. You can use before_deliver
, around_deliver
and after_deliver
in your custom delivery methods.
class DeliveryMethods::Discord < Noticed::DeliveryMethods::Base
after_deliver do
# Do whatever you want
end
end
Rails 6.1+ can serialize Class and Module objects as arguments to ActiveJob. The following syntax should work for Rails 6.1+:
deliver_by DeliveryMethods::Discord
For Rails 5.2 and 6.0, you must pass strings of the class names in the deliver_by
options.
deliver_by :discord, class: "DeliveryMethods::Discord"
We recommend using a string in order to prevent confusion.
The Notification database model includes several helpful features to make working with database notifications easier.
Sorting notifications by newest first:
user.notifications.newest_first
Query for read or unread notifications:
user.notifications.read
user.notifications.unread
Marking all notifications as read or unread:
user.notifications.mark_as_read!
user.notifications.mark_as_unread!
Convert back into a Noticed notification object:
@notification.to_notification
Mark notification as read / unread:
@notification.mark_as_read!
@notification.mark_as_unread!
Check if read / unread:
@notification.read?
@notification.unread?
Adding notification associations to your models makes querying and deleting notifications easy and is a pretty critical feature of most applications.
For example, in most cases, you'll want to delete notifications for records that are destroyed.
We'll need two associations for this:
- Notifications where the record is the recipient
- Notifications where the record is in the notification params
For example, we can query the notifications and delete them on destroy like so:
class Post < ApplicationRecord
# Standard association for deleting notifications when you're the recipient
has_many :notifications, as: :recipient, dependent: :destroy
# Helper for associating and destroying Notification records where(params: {post: self})
has_noticed_notifications
# You can override the param_name, the notification model name, or disable the before_destroy callback
has_noticed_notifications param_name: :parent, destroy: false, model_name: "Notification"
end
# Create a CommentNotification with a post param
CommentNotification.with(post: @post).deliver(user)
# Lookup Notifications where params: {post: @post}
@post.notifications_as_post
CommentNotification.with(parent: @post).deliver(user)
@post.notifications_as_parent
If you create a notification but delete the associated record and forgot has_noticed_notifications
on the model, the jobs for sending the notification will not be able to find the record when ActiveJob deserializes. You can discord the job on these errors by adding the following to ApplicationJob
:
class ApplicationJob < ActiveJob::Base
discard_on ActiveJob::DeserializationError
end
This project uses Standard for formatting Ruby code. Please make sure to run standardrb
before submitting pull requests.
Running tests against multiple databases locally:
DATABASE_URL=sqlite3:noticed_test rails test
DATABASE_URL=mysql2://root:@127.0.0.1/noticed_test rails test
DATABASE_URL=postgres://127.0.0.1/noticed_test rails test
The gem is available as open source under the terms of the MIT License.