My latest story card is all about getting our app to send email so I’ve had to delve into ActionMailer. It’s a cool way to send emails without tons of code but there are some things that are a little bit wacky. Let us say that you’ve got a cookie website and you want people to be able to send emails of their favorite recipes to each other. “My that’s a handsome cookie” they might say and “Oh look, an email button that lets me send this recipe to a friend. This site has met my needs exactly.” In order to get such a thing to happen you would create a RecipeMailer model that extended ActionMailer kinda like this:
class RecipeMailer < ActionMailer::Base
def recipe_email(email_params)
@from = email_params[:from]
@recipients = email_params[:recipients]
@subject = email_params[:subject]
@sent_on = email_params[:sent_on]
@body[:recipe] = email_params[:recipe]
end
end
In your controller you’d call:
RecipeMailer.deliver_recipe_email(email_params)
And some good old method_missing magic would match deliver_recipe_email with your recipe_email method which would then render a view.
Er, a view?
That’s right, the email templates are rhtml views. You write them as you would a view, but ActionMailer turns them into emails. So one might look like this:
Howdy neighbor,
Here’s a recipe you might like:
<%= @recipe.text %>
And Rails would of course evaluate the <%= stuff like it would in a view. But, although it’s a view, it doesn’t have access to any helper methods you have defined, even if you put them in ApplicationHelper (which are supposed to be available to all views). You’ll have to declare this in your RecipeMailer model if you want the view to see them.
ApplicationHelper:
helper :application
Another thing to watch out for is any links you include in your email must have the full path to your app. When using link_to you can pass in these options:
:only_path => false, :host => @host
Where host is the host you get off of request.host but you won’t have access to request in your view or your mailer, so you’ll have to pass it in from your controller (again, the email template is only sort of a view). But if you’ve added some fancy custom tags (like a hypothetical recipe_image_tag that takes in a recipe object and does all sorts of neato stuff) you might find that the combination of needing a full path and being rendered by an email client (ie not your rails app) will force you to, sigh, hack up your own links like so:
<img src="http://<%= @host -%>/images/logos/<%=@cookie_name-%>.png"
alt="<%=@cookie_name-%>." />
Not the prettiest code I’ve ever written but sometimes time is a factor.


13 comments:
Actually, the best way to set the host from the request would be like so:
default_url_options[:host] = request.host
Now you can use link_to rather than having to do all that string interpolation.
Oh, I forgot to mentions, set that at the class level
class WhateverMailer < ActionMailer::Base
default_url_options[:host] = request.host
def cookie_email(params)
#...
end
end
So I tried that but I get this error:
undefined local variable or method `request' for CookieMailer:Class
I just don't seem to have access to the request in the ActionMailer class.
Jake, found your blog whilst having the same problem. It's dirty, but I've got around this issue by adding:
ActionMailer::Base.default_url_options[:host] = request.host_with_port
to my ApplicationController, in a filter method. That sets the defaults on each mailer.
I'm not sure why I have to set it on the mailers - I wanted to set it on the ApplicationController so it was accessible everywhere but didn't manage that. Never mind, I've only had this problem with mailers so that should sort it.
Hi Jake, good post on ActionMailer. I had the same problem with designating a host in link_to's on the view template.
I was able to get around this by using named routes. eg:
routes.rb
map.index 'home', :controller => 'home', :action => 'index'
mail view template
<\%\= link_to 'go', index_url(:host => @host) \%\>"
where @host is the domain name passed in by the mailer model.
Unfortunately, a regular link_to doesn't seem to like this as much.
Thanks for the nice post!
Great; thanks for the help on including helper methods in ActionMailer!
Any luck on using SMTP via SSL? I know there is a gem (tlsmail) for connecting via TSL, but I cannot find a solution for SSL.
Anyone?
Thanks that was helpful!
If you'd like a more sane way of linking t your app in ActionMailer emails, try doing a google search for 'retardase_inhibitor'. The name might throw you askew, but the summary of it is as follows:
---BEGIN README BLURB
RetardaseInhibitor
==================
It is difficult to generate URLs with ActionMailer. Although you can generate
absolute paths from your routes, you usually need to generate full URLs for
the links in the email to be useful to your recipients.
This plugin sets the default_url_options for host, port and protocol by
providing an around_filter which will grab the values from the request object.
In the cases when the request object is not available, for example, when
running tests, default values are set instead.
See the following blog post for more details:
http://www.pivotalblabs.com/articles/2007/08/20/how-i-leaned-to-stop-hating-and-love-action-mailer
----END README BLURB
apart from the "leaned" typo, that url seems to have swollen to http://pivots.pivotallabs.com/users/nick/blog/articles/281-how-i-learned-to-stop-hating-and-love-action-mailer
What a pretty picture in your blog header. Wow!
There is a useful Action Mailer cheatsheet here: http://dizzy.co.uk/ruby_on_rails/cheatsheets/action-mailer
Post a Comment