Pass data to mailer daemon in Rails? - ruby-on-rails

According to the Rails API (snippet below), the optimal way to receive mail is by creating a single Rails instance within a daemon that gets invoked by a MTA whenever new mail arrives.
My question is: how do you pass data to that daemon when new mail arrives?
========================
Rails API Snippet
To receive emails, you need to implement a public instance method called receive that takes a tmail object as its single parameter. The Action Mailer framework has a corresponding class method, which is also called receive, that accepts a raw, unprocessed email as a string, which it then turns into the tmail object and calls the receive instance method.
Example:
class Mailman < ActionMailer::Base
def receive(email)
page = Page.find_by_address(email.to.first)
page.emails.create(
:subject => email.subject, :body => email.body
)
if email.has_attachments?
for attachment in email.attachments
page.attachments.create({
:file => attachment, :description => email.subject
})
end
end
end
end
This Mailman can be the target for Postfix or other MTAs. In Rails, you would use the runner in the trivial case like this:
./script/runner 'Mailman.receive(STDIN.read)'
However, invoking Rails in the runner for each mail to be received is very resource intensive. A single instance of Rails should be run within a daemon if it is going to be utilized to process more than just a limited number of email.

In the example you provide, there is no daemon running to process email. The documentation is saying you can setup your mailer daemon, Postfix in this case, to invoke a command when mail is received. When you call the command from your mailer:
RAILS_ROOT/script/runner 'Mailman.receive(STDIN.read)'
The content of the email is passed into the receive method. A much better way to handle processing incoming email is to create an actual mailbox that receives email. You can then write a Ruby script that batch checks the mailbox to process the email. You can call that script via cron with lock run around it to insure that there is only one process performing this task.

Related

Is it possible to forward the mail object in Action Mailer

We're currently letting users email each other without seeing each other's actual email addresses (double blind) by letting them send emails to username#parse.example.com which works great.
class ForwardsMailbox < ApplicationMailbox
before_processing :ensure_users
def process
content = mail.multipart? ? mail.parts.first.body.decoded : mail.decoded
UserMailer.with(sender: sender, recipient: recipient, subject: mail.subject, content: content).forward_email.deliver_later
end
private
def sender
#sender ||= User.find_by(email: mail.from.first)
end
def recipient
#recipient ||= User.find_by(username: mail.to.first.split('#').first)
end
def ensure_users
bounce_with UserMailer.invalid_user(inbound_email) if sender.nil? or recipient.nil?
end
end
Is it possible to forward the whole mail object instead of extracting its contents, checking if it is multipart etc?
Try to give this a shot. Reuse the mail object in your process method and directly deliver the message yourself. You'll need to reach into ActionMailer to get your delivery methods configured correctly, but I believe it'll work.
def process
mail.to = sender.email
mail.from = recipient...
ActionMailer::Base.wrap_delivery_behavior(mail) # this sets delivery to use what we specified in our rails config.
mail.deliver # This delivers our email to the smtp server / API
end
How this works:
Behind the scenes Mailers are just calling deliver on a Mail object to send emails. You can go peruse through ActionMailer::MessageDelivery if you'd like to see that in action. We're just using that functionality directly here.
I'd recommend staying away from using Mailers in this instance because copying over all the fields from your original mail object to the mailer's mail object is going to require a lot of trial and error.
One thing to note: the headers remain unchanged when the message is re-delivered, so things like Message-ID will still be the same (which may or may not be a problem, just something to consider).
Last, if you're concerned at all about deliver being a blocking call to an API/SMTP server like I was, worry not! It looks like ActionMailbox already ensures that the process method runs via ActiveJob, so you shouldn't have to worry about an SMTP/API request taking a while and blocking a web request (see ActionMailbox guides).

Rails 4 controller won't call my actionmailer class

In my rails 4.2.5 (ruby 2.2.1) app I have an actionmailer class that is so simple. It doesn't even send mail, just does a printf:
class UserTommail < ActionMailer::Base
def joe
printf("\n***** In Emails.joe")
end
end
But when my controller calls this function, it never does the printf!
def contact_us
printf("\n***** TOMS EMAILLER")
UserTommail.joe()
redirect_to(root_path(), :notice => "Your Contact Us message has been successfully sent.")
printf("\n**** TOMS END")
end
The two printfs in the controller actually print their message, but the one in joe() never does. No errors or anything.
If I sabotage joe() to say joe() in the file user_tommail.rb, I get an error that the function can't be found, so I know the controller knows about it.
What am I doing wrong?
You should call deliver_now to fire the mailer to send the email:
UserTommail.joe.deliver_now
See the full list of available methods in docs.
For Rails 4 to send a an email
UserTommail.joe.deliver_now
or
UserTommail.joe.deliver_later #Enqueues the email to be delivered through Active Job. When the job runs it will send the email using deliver_now.
For Rails 3 to send a an email
UserTommail.joe.deliver
The problem here is that ActionMailer tries to be "clever" about what it does, and won't actually call your mailer method until its return value is needed.
Your mailer method joe will return an ActionMailer::MessageDelivery object, which wraps a Mail::Message object (even though you haven't specifically said you want to send an email). The Mail::Message gets lazily evaluated, meaning it won't be instantiated (and your method won't be called) until it's needed.
One way of forcing the evaluation would be to try and send the returned email with deliver_now or deliver_later, but another way would simply be to inspect the message.
if you had my_email = UserTommail.joe() and then called my_email.message, it would force the method to be run and you would see your printf in the console.

Sidekiq: Pass email body and attachments for processing?

I just learned/started using Sidekiq today to handle background processing of incoming email messages and attachments, but am a bit lost on the best way to get the email body and attachments into the worker for processing.
My RoR app is hosted on Heroku and receives incoming emails via Mailgun to a controller, which then kicks off my worker. Within the worker is a call to a 3rd party API to upload my email messages and attachments (think DropBox.)
Mailgun pre-parses everything and sends it over as parameters, but from what I understand about Sidekiq, I don't want to pass along entire objects such as the email body and/or attachments as shown here.
#attach_count = params["attachment-count"]
#from = params["from"]
#subject = params["subject"]
#msgbody = params["body-html"]
ProcessEmailWorker.perform_async(#id, #attach_count, #from, #subject, #msgbody)
What's the best practice for getting these items over to my worker?
I assume Mailgun is POSTing to your controller.
You can send the POST body as a single string parameter to Sidekiq and have it re-parse everything.
You can save the data to Redis or your database for processing in Sidekiq.
You can send the email content as a Hash of Strings:
{ 'subject' => ..., 'body' => ... }
After speaking with another developer I chose to do the following:
Set up a route in Mailgun to store the incoming email message, but to post a notification to my controller
Have my controller grab the incoming message ID and pass that along to my worker
From within my worker, use the message ID to perform a GET to Mailgun to retrieve the stored message (and its attachments)
Process the message/attachments and upload them to my cloud storage provider.

ActionMailer workflow

I am newbie in Rails, therefore sorry for silly question. For sending emails I use ActionMailer with Rails 2.3.5. The syntax is following
deliver_maintest1
deliver_maintest2
in model of instance of ActionMailer I have
def maintest1
end
def maintest2
end
Inside definitions I set recipient, subject, headers,...As I understand there is no any explicity defined method mail which is actually sent email. Emails are sent from def maintest1 and maintest2. The problem is before sending email I need to define few counters how many emails were sent thought maintest1 and maintest2. Now take into account I have tens defs like maintest. So I need a common place for all those defs. In your opinion what's the best solution?
Thanks!
On rails 3 and above you could use an observer. These get called after every mail delivery, passing through the message object. You just need to implement a delivered_email class method and register it.
class EmailObserver
def self.delivered_email(message)
# do something with message
end
end
Then, hook it into mail with
Mail.register_observer(EmailObserver)
This doesn't work on rails 2.x, which doesn't use the mail gem (it uses tmail from the ruby standard library.)
On 2.3.x I would try something like
class MyMailer < ActionMailer::Base
def deliver!(mail=#mail)
super
# do your logging here
end
end
You would be calling "Mailer.deliver_maintest" to send the mails out to anyone, to count the nos of times you sent a particular email you just need to keep track of it each time you call "Mailer.deliver_maintest" .
You can store that counter either the database or somewhere. something like this.
// Some lines of code to Update the counter for the mailer
Mailer.deliver_maintest
You can also use a third party email tool like PostMark to send your email ( with them you can associate each email with tags, and I just generally use those tags to keep track of emails sent out ).

How do I capture the output of an email in Rails and put it in a variable

The output from an email sent from ActionMailer output to the log, I wanted to know if I could get that output into a variable so I can store it in a file.
Ps. I forgot to mention that this is on Rails 2
As McStretch has pointed out, observer is the best way to handle every message that is delivered by a mailer. However, if you'd like to just capture 1 or 2 special cases, you can do the following:
Assuming you have an ActionMailer subclass called MyMailer, and an email called foobar,
# Rails 2.x
mail = MyMailer.create_foobar(...) # instead of MyMailer.deliver_foobar(...)
File.open('filename.txt', 'wb') {|f| f.write(mail.body) }
MyMailer.deliver(mail)
# Rails 3.x
mail = MyMailer.foobar(...) # instead of MyMailer.foobar(...).deliver
File.open('filename.txt', 'wb') {|f| f.write(mail.body) }
mail.deliver
You can use the register_interceptor or register_observer methods on ActionMailer to do something before or after sending the mail, respectively. The ActionMailer docs state:
Action Mailer provides hooks into the
Mail observer and interceptor methods.
These allow you to register objects
that are called during the mail
delivery life cycle.
An observer object must implement the
:delivered_email(message) method which
will be called once for every email
sent after the email has been sent.
An interceptor object must implement
the :delivering_email(message) method
which will be called before the email
is sent, allowing you to make
modifications to the email before it
hits the delivery agents. Your object
should make and needed modifications
directly to the passed in
Mail::Message instance.
Each of these methods provide a Mail::Message as an argument, so you should be able to get the desired data from that object and save it somewhere:
class MyInterceptor
def self.delivering_email(mail)
# do something before sending the email
end
end
class MyObserver
def self.delivered_email(mail)
# do something after sending the email
end
end
Example above from http://blog.envylabs.com/2010/04/new-in-rails3-beta2/

Resources