ActionMailer tips
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:
In your controller you’d call:
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:
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.
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:
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:
Not the prettiest code I’ve ever written but sometimes time is a factor.
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.
Comments
default_url_options[:host] = request.host
Now you can use link_to rather than having to do all that string interpolation.
class WhateverMailer < ActionMailer::Base
default_url_options[:host] = request.host
def cookie_email(params)
#...
end
end
undefined local variable or method `request' for CookieMailer:Class
I just don't seem to have access to the request in the ActionMailer class.
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.
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.
Anyone?
---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