---
title: I Accidentally the Whole SMTP Exception
teaser:
tags: web,ruby
author: Mike Burns
published_on: 2008-12-22
---

You have a slick, exclusive, invite-only Web app for sharing Tor <abbr
title="Uniform Resource Locator">URL</abbr>s, with an Android client and
specialty hardware. You use
[validates_email_format_of](http://code.dunae.ca/validates_email_format_of.html)in
the `Invitation` model, but still something slips through and your
[Hoptoad](http://hoptoadapp.com/) 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`:

```ruby
SMTP_SERVER_ERRORS = [
  IOError,
  Net::SMTPAuthenticationError,
  Net::SMTPServerBusy,
  Net::SMTPUnknownError,
  TimeoutError,
]

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`:

```ruby
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`:

```ruby
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](https://github.com/thoughtbot/suspenders) you'll be
pleased to find that we've included
[`config/initializers/errors.rb`](http://github.com/thoughtbot/suspenders/tree/master/config/initializers/errors.rb)
for you pre-populated with both SMTP and [HTTP
exceptions](http://tammersaleh.com/posts/rescuing-net-http-exceptions).
