You have a slick, exclusive, invite-only Web app for sharing Tor URLs, with
an Android client and specialty hardware. You use validates_email_format_of
in the Invitation model, but still something slips through and
your Hoptoad errors pile up, showing
your user the beautifully-designed 500 page instead of an error
explanation.
There are two types of exceptions that ActionMailer will raise
when you attempt to deliver an email: user input problems and server
problems.
User input problems are those such as incorrect or invalid email addresses;
the exceptions raised are Net::SMTPFatalError and
Net::SMTPSyntaxError. These are issues that the user can fix and as such the error message should indicate that everything’s fine, nothing is ruined.
Server problems could be anything from a non-existent server to an
authentication issue; the exceptions raised are: TimeoutError,
IOError, Net::SMTPUnknownError,
Net::SMTPServerBusy, and
Net::SMTPAuthenticationError. These issues are outside the power of the user and should indicate that we screwed up.
So in config/initializers/errors.rb:
SMTP_SERVER_ERRORS = [TimeoutError,
IOError,
Net::SMTPUnknownError,
Net::SMTPServerBusy,
Net::SMTPAuthenticationError]
SMTP_CLIENT_ERRORS = [Net::SMTPFatalError, Net::SMTPSyntaxError]
SMTP_ERRORS = SMTP_SERVER_ERRORS.concat(SMTP_CLIENT_ERRORS)
SMTP_CLIENT_ERROR_FLASH = 'The email address supplied is invalid. Please check for spelling mistakes.'
SMTP_SERVER_ERROR_FLASH = 'We encountered an internal issue while attempting to deliver this email. Please try again in a few minutes.'
We can test it with invitations_controller_test.rb:
class InvitationsController; def rescue_action(e) raise e end; end
class InvitationsControllerTest < Test::Unit::TestCase
SMTP_CLIENT_ERRORS.each do |exn|
should "handle #{exn}" do
InvitationsMailer.expects(:deliver_invitation).raises(exn)
post :create, :invitation => {:email => 'invalid email'}
assert_match /#{SMTP_CLIENT_ERROR_FLASH}/i, @response.flash[:warning]
assert_template 'new'
end
end
SMTP_SERVER_ERRORS.each do |exn|
should "handle #{exn}" do
InvitationsMailer.expects(:deliver_invitation).raises(exn)
post :create, :invitation => {:email => 'invalid email'}
assert_match /#{SMTP_SERVER_ERROR_FLASH}/i, @response.flash[:warning]
assert_template 'new'
end
end
end
And in invitations_controller.rb:
class InvitationsController < ApplicationController
def create
@invitation.new(params[:invitation])
if @invitation.save
redirect_to root_url
else
render :action => 'new'
end
rescue *SMTP_CLIENT_ERRORS
flash[:warning] = SMTP_CLIENT_ERROR_FLASH
render :action => 'new'
rescue *SMTP_SERVER_ERRORS => error
notify_hoptoad error
flash[:warning] = SMTP_SERVER_ERROR_FLASH
render :action => 'new'
end
end
If you use Suspenders you’ll be pleased to find that we’ve included config/initializers/errors.rb for you pre-populated with both SMTP and HTTP exceptions.