I am on Heroku with a custom domain, and I have the Redis add-on. I need help understanding how to create a background worker for email notifications. Users can inbox message each other, and I would like to send a email notification to the user for each new message received. I have the notifications working in development, but I am not good with creating background jobs which is required for Heroku, otherwise the server would timeout.
Messages Controller:
def create
#recipient = User.find(params[:user])
current_user.send_message(#recipient, params[:body], params[:subject])
flash[:notice] = "Message has been sent!"
if request.xhr?
render :json => {:notice => flash[:notice]}
else
redirect_to :conversations
end
end
User model:
def mailboxer_email(object)
if self.no_email
email
else
nil
end
end
Mailboxer.rb:
Mailboxer.setup do |config|
#Configures if you applications uses or no the email sending for Notifications and Messages
config.uses_emails = false
#Configures the default from for the email sent for Messages and Notifications of Mailboxer
config.default_from = "no-reply#domain.com"
#Configures the methods needed by mailboxer
config.email_method = :mailboxer_email
config.name_method = :name
#Configures if you use or not a search engine and which one are you using
#Supported enignes: [:solr,:sphinx]
config.search_enabled = false
config.search_engine = :sphinx
end
Sidekiq is definitely the way to go with Heroku. I don't think mailboxer supports background configuration out of the box. Thankfully, it's still really easy with sidekiq's queueing process.
Add gem 'sidekiq' to your gemfile and run bundle.
Create a worker file app/workers/message_worker.rb.
class MessageWorker
include Sidekiq::Worker
def perform(sender_id, recipient_id, body, subject)
sender = User.find(sender_id)
recipient = User.find(recipient_id)
sender.send_message(recipient, body, subject)
end
end
Update your Controller to Queue Up the Worker
Remove: current_user.send_message(#recipient, params[:body], params[:subject])
Add: MessageWorker.perform_async(current_user.id, #recipient.id, params[:body], params[:subject])
Note: You should never pass workers ActiveRecord objects. That's why I setup this method to pass the User ids and look them up in the worker's perform method, instead of the entire object.
Finally, restart your server and run bundle exec sidekiq. Now your app should be sending the email background.
When you deploy, you will need a separate dyno for the worker which should look like this: worker: bundle exec sidekiq. You will also need Heroku's redis add-on.
Sounds like a H21 Request Timeout:
An HTTP request took longer than 30 seconds to complete.
To create a background worker for this in RoR, you should grab Resque, a Redis-backed background queueing library for RoR. Here is a demo. Another demo. And another demo.
To learn more about using Resque in Heroku, you can also read the herokue article up here. Or this tutorial (it's an old one though). Another great tutorial.
There is also a resque_mailer gem that will speed things up for you.
gem install resque_mailer #or add it to your Gemfile & use bundler
It is fairly straightforward. Here is a snippet from a working demo by the author:
class Notifier < ActionMailer::Base
include Resque::Mailer
default :from => "from#example.com"
def test(data={})
data.symbolize_keys!
Rails.logger.info "sending test mail"
Rails.logger.info "params: #{data.keys.join(',')}"
Rails.logger.info ""
#subject = data[:subject] || "Testing mail"
mail(:to => "nap#localhost.local",
:subject => #subject)
end
end
doing Notifier.test.deliver will deliver the mail.
You can also consider using mail delivery services like SES.
Sidekiq is an option that you could consider. To get it working you can add something like RedisToGo, then configure an initializer for Redis. Then on Heroku you can add something like worker: bundle exec sidekiq ... to your Procfile.
https://github.com/mperham/sidekiq/wiki/Getting-Started
It also has a dashboard for monitoring.
https://github.com/mperham/sidekiq/wiki/Monitoring
Related
I have two rails applications, App1 and App2(added cloudAMQP gem) in heroku, App1 is producing some message when click on a button
App1
class Publisher
def publish
# Start a communication session with RabbitMQ
connection = Bunny.new(:host => "chimpanzee.rmq.cloudamqp.com", :vhost => "test", :user => "test", :password => "password")
connection.start
# open a channel
channel = connection.create_channel
# declare a queue
queue = channel.queue("test1")
# publish a message to the default exchange which then gets routed to this queue
queue.publish("Hello, everybody!")
end
end
so in the App2 i have to consume all the messages without any button click and put that in sidekiq to process the data, but i am stuck on how can i automatically read from that queue, i know the code how to read values from queue, people are saying sneakers gem, but i am bit confused with sidekiq and sneakers, any idea of how can we do it in heroku?
To read the messages you publish from App1 to App2, in App2 you gonna need sneakers (https://github.com/jondot/sneakers)
your reader would do something like:
class Reader
include Sneakers::Worker
from_queue 'test1'
def work(message)
# your code
ack!
end
end
and you need to configure your environment, you can take a look at https://github.com/jondot/sneakers/wiki/Configuration
Say I rescue from an Exception and I do:
begin
raise StandardError
rescue StandardError => ex
ExceptionNotifier.notify_exception(ex)
end
end
How can I make that ExceptionNotifier email be sent from a queue? So, it is asynchronous to the process of the application?
In the docs I can see how to send ExceptionNotifier if the error has happened within a worker, but not how to enqueue that sending to a queue.
The queue aspect of Rails has to be handled by a third-party semi-persistent data store. We use Redis & Resque
--
Here is a good tutorial on this:
Initializer
#app/config/initializers/redis.rb
require 'resque/server' #-> allows processing of jobs
require 'resque_scheduler' #-> allows for scheduling
uri = URI.parse(ENV["REDISCLOUD_URL"] ||= "http://localhost:6379")
Resque.redis = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)
-
Resque
This will allow you to send data to redis, using your Resque queue to handle it:
def your_action
Resque.enqueue(SendEmail, [[data ref]])
end
-
Queue
Then you can use resque to run through the Redis queue & send the emails:
$ rake resque:work QUEUE='*'
Quite a vague description, I know; but hopefully it will give you an idea as to how to use a third-party queue-based system to handle sending emails for you
I'm using the whenever gem to have a rails cron job send emails. Everything seems to work just fine and i have no errors in my cron.log or my production.log file, but i never receive an email. I've checked that the email address is correct also.
Any help is appreciated.
The production.log file contains this:
Connecting to database specified by database.yml
Rendered email_mailer/send_birthday_reminders.html.erb (5.3ms)
Sent mail to tomcaflisch#gmail.com (409ms)
Here's my whenever gem schedule.rb file
set :output, "#{path}/log/cron.log"
every :hour do
runner "BirthdayRemindersController.send_birthday_email_reminders"
end
birthday_reminders_controller.rb
class BirthdayRemindersController < ApplicationController
# cron job that sends birthday reminders
def self.send_birthday_email_reminders
users = User.all
email_addresses = []
users.each_with_index do |user, i|
if user.user_details.birthday_reminders == true
email_addresses[i] = get_primary_email(user)
end
end
p "email_addresses to send to:"
p email_addresses
users.each do |user|
p "this user is"
p user.user_details.full_name
if user.user_details.birthday.try(:strftime, "%m") == Date.today.strftime("%m") && user.user_details.birthday.try(:strftime, "%d") == Date.today.strftime("%d")
p "reminder sent"
EmailMailer.send_birthday_reminders(user, email_addresses).deliver
end
end
end
end
email_mailer.rb snippet
class EmailMailer < ActionMailer::Base
include ApplicationHelper
default :from => "\"FamNFo\" <no-reply#mysite.com>"
def send_birthday_reminders(birthday_person, email_addresses)
p "we in send_birthday_reminders mailer"
p email_addresses
#birthday_person = birthday_person
mail(:subject => "Birthday Reminder For The Caflisch Family", :to => email_addresses, :reply_to => email_addresses)
end
end
capistrano's deploy.rb contains this
# needed for the 'whenever' gem
set(:whenever_command) { "RAILS_ENV=#{rails_env} bundle exec whenever"}
require "whenever/capistrano"
Check your spam folder. To make sure emails don't end up there, add an "Unsubscribe" link in each email.
This could happen if your action mailer configuration specifies perform_deliveries=false. You can check out the configuration in your environment files.
If your application is deployed to cloud services then you may be getting your emails in a spam folder. Their entire IP blocks are registered as spam at services like Spamhaus, which is a sensible precaution or else we'd be getting even more spam than usual.
You should enter your server's IP address in that field to see if you're listed as a spammer.
If you are, you can request to Spamhaus that the block be lifted.
The other big issues I have found is that the PATH and rbenv may not be initialized in the CRONTAB depending on how you have it setup.
I would recommend adding the following to the top of your .bashrc file
export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"
This ensures that if you are using whenever to call model methods that rbenv and ruby are fully available.
I'm trying to send emails using delayed job from my Rails 3.2 app on Heroku when someone fills out a form. I've been able to get the emails to send successfully through delayed job on my local development machine. I can also get the emails to send using delayed job if I run them manually through the console on Heroku. However, when someone submits a form which triggers the email, it will not send.
Here's my mailer:
class ClaimMailer < ActionMailer::Base
default from: "noreply#coatchex.com"
def patron(claim)
mail(:to => claim.email, :subject => I18n.t('mailers.claims.patron.subject'))
end
def coatchex(claim)
#claim = claim
mail(:to => 'claims#coatchex.com', :subject => I18n.t('mailers.claims.coatchex.subject'))
end
end
Here's my controller:
class ClaimsController < ApplicationController
layout 'basic'
def new
#claim = CoatChex::Forms::Claim.new
end
def create
#claim = CoatChex::Forms::Claim.new(params[:claim])
if #claim.valid?
ClaimMailer.delay.coatchex(#claim)
render :thank_you
else
render :new
end
end
end
Like I mentioned, If I run the following command through the Heroku console it queues the email up in delayed job and sends it just fine:
#claim = ...
ClaimMailer.delay.coatchex(#claim)
However, whenever I send it through the form, it does not trigger.
If I'm quick enough I can run Delayed::Job.count in the Heroku console and see a value of 1 before the job executes when submitting through the form. So I know delayed job is getting it. If I look at the worker logs using
heroku logs -p worker -t
I can see the job process getting logged when executing it manually but not when it goes through the form.
There are no failed jobs in the database.
Anybody run into anything like this before?
I had a similar problem. A good starting point was the information at https://devcenter.heroku.com/articles/delayed-job#debugging - specifically, running Delayed::Job.last.last_error on the Heroku console.
In my case, the error I was getting was Job failed to load: uninitialized constant Syck::Syck, which was fixed by adding psych to my gemfile. See Delayed_job: Job failed to load: uninitialized constant Syck::Syck and http://effectif.com/ruby-on-rails/syck-and-psych-yaml-parsers-on-heroku
You need to start the worker with the command
rake jobs:work
on your heroku rails console.
I am writing a rails app which requires to track users' status to see if they are available, busy or offline. I'm using the private_pub gem, which uses Faye underneath. When a user signs in he subscribes to a channel /user/[:user_id]. I want to update user's status to ONLINE when they subscribe using Faye's subscribe event listener. I added this code at the end of private_pub.ru file:
server = PrivatePub.faye_app
server.bind :subscribe do |client_id, channel|
if /\/user\/*/.match(channel)
m = /\/user\/(?<user_id>\d+)/.match(channel)
user_id = m[:user_id]
end
user = User.find(user_id)
user.status = 1 # 1 means online
end
run server
The problem is every time a user subscribes, thin server reports:
[ERROR] [Faye::RackAdapter] uninitialized constant User
I guess I need to require certain files to be able to use activerecords in the rackup file. But I don't know how.
Thanks for any help.
In our project we decide to use redis for similar case.
Gemfile:
gem 'redis-objects'
Faye: use redis-rb for writing status
require 'redis'
Redis.current = Redis.new(:host => '127.0.0.1', :port => 6379)
# init faye server
...
server.bind(:subscribe) do |client_id, channel|
if /\/user\/*/.match(channel)
m = /\/user\/(?<user_id>\d+)/.match(channel)
Redis.current.set("user:#{m[:user_id]}:online_status", "1")
end
end
Rails: use redis-objects gem for reading it in User's model.
class User < ActiveRecord::Base
include Redis::Objects
value :online_status
end
#user.online_status # returns "1" if channel is connected
Hope this helps.