email_campaign

For mailings related to Rails apps, email_campaign is designed to be a more flexible, more integrated (yet well-abstracted) alternative to email campaign services like Campaign Monitor and MailChimp. Developed using SendGrid for delivery (via the ActionMailer::Base.smtp_settings), but feel free to use whatever you want.

Roadmap

0.x: Basic campaign authoring, delivery and tracking. 1.0: Robust enough for production use, will be able to deal with 90% of common failure modes on its own. 1.2: Delivery stats and graphs.

Current Status

Still pre-1.0. If you have some time, write some code or tests, exercise existing functionality on non-critical apps, submit bug reports… otherwise stay away for now.

How to Use

Add to Gemfile and install:

gem 'email_campaign'

# required
gem 'delayed_job'
gem 'daemons'

# pick a dj backend (we'll assume ActiveRecord, but there are others):
gem 'delayed_job_active_record

# recommended but not required
gem 'sanitize_email'
gem 'inline-style'

Run migrations:

rake email_campaign:install:migrations
rails generate delayed_job:active_record
rake db:migrate

Create a mailer, be sure to use EmailCampaign::EmailHelper:

class Campaign < ActionMailer::Base
  default from: '[email protected]'
  helper EmailCampaign::EmailHelper
  layout 'campaign'

  def campaign2013a(recipient)
    # @method, and @identifier are used by our EmailHelper for tracking
    @method = __method__
    @identifier = recipient.identifier

    # @subject is used so we can use the same template for email and web
    @subject = 'First mailing with email_campaign'
    mail(:to => recipient.to_s, :subject => @subject)
  end
end

Add some of these to your mailer layout and template:

<!-- web version link -->
<%- if @identifier -%>
<div id="top-notice">
  Not seeing any images in this email? <a href="<%= email_web_version_url %>">View it online &raquo;</a>
</div>
<%- end -%>

<!-- trackable link -->
<%= email_link_url('http://www.mysite.com/') %>

<!-- unsubscribe link -->
<%- if @identifier -%>
  <p>If you do not wish to receive these emails, you may <a href="<%= email_unsubscribe_url %>">unsubscribe now</a>.</p>
<%- end -%>

<!-- inserts invisible tracking image to track opens -->
<%= email_tracking_tag %>

You’ll need a controller to handle tracking, unsubscribe/resubscribe and web versions.

class EmailController < ApplicationController
  # reuse the email layout (optional, but recommended)
  layout 'campaign'

  # include the helper, just like we did in the mailer
  helper EmailCampaign::EmailHelper

  # adds #open, #link, #unsubscribe/#resubscribe, #web_version
  include EmailCampaign::Handler

  # must be named the same way as your mailer method
  # Again, @method is used for tracking, and  @subject is used so we can use the same template for email and web.
  # Here, we copy the subject directly from the mailer. Not pretty, but does avoid duplication.
  def campaign2013a
    @method = __method__
    @subject = Campaign.send(__method__, EmailCampaign::Recipient.new).subject
    render :template => '/campaign/campaign2013a'
  end
end

Add routes. In this case, the old-style default works well:

match 'email(/:action(/:id))' => 'email'

Finally, create an initializer that sets base_url (since urls in emails need to include hostname and protocol) and optionally controller_name (to tell email_campaign about the controller you created above):

# Set base url that will be prepended to url paths in emails
EmailCampaign::Config.base_url = ENV['BaseURL'] || 'http://mysite.com'

# (Optional, can be auto-detected) Specify the name of the controller that includes
# EmailCampaign::Handler as it would appear in url_for(:controller => '...')
EmailCampaign::Config.controller_name = 'email'

Okay, now how do we actually send this thing? You’ll want to make a controller for this eventually, but for now let’s use the console:

# create a campaign, making sure to specify the mailer class and method we created earlier
c = EmailCampaign::Campaign.find_or_create_by_name(:name => 'Campaign 2013a')
c.mailer = 'Campaign'
c.method = 'campaign2013a'
c.save

# recipients can be any object that responds to #name, #email_address and #id (or optionally, #subscriber_id)
class TestRecipient
  attr_accessor :name, :email_address, :subscriber_id
  def initialize(name, email_address, subscriber_id)
    @name = name ; @email_address = email_address ; @subscriber_id = subscriber_id
  end
end

rcpts = [ TestRecipient.new('Aaron Namba', '[email protected]', 1) ]

# this will create EmailCampaign::Recipient objects (c.recipients), which contain delivery status
# information (e.g. #delivered, #failed, #failure_reason, #opened, #clicked)
counts = c.add_recipients(rcpts, :force => true)

if c.queue
  # use dj to send in 5 minutes; deliver! also uses dj to queue up individual emails
  c.delay(:run_at => 5.minutes.from_now).deliver!
  puts "Emails queued for immediate delivery. #{counts[:processed]} processed, #{counts[:skipped]} skipped, #{counts[:valid]} valid, #{counts[:invalid]} invalid, #{counts[:duplicate]} duplicate, #{counts[:unsubscribed]} unsubscribed, #{counts[:total]} total in queue."
end

Start dj if you haven’t already:

script/delayed_job start

Getting Help

Get paid support for EmailCampaign straight from the people who made it: Bigger Bird Creative, Inc. Not required, of course. :-) Free support is up top (Issues).

Customizing & Contributing

Pull requests always appreciated (recommend getting in touch first). If companies or individuals are willing to sponsor major features on the roadmap (or features that meet their own needs) development can proceed more quickly.

Dependencies

Developed and tested with MRI ruby 1.9.3.

Dependencies:

  • mail (email)

License & Copyright

Distributed under MIT license. Copyright © 2013 Aaron Namba <[email protected]>

<img src=“https://travis-ci.org/anamba/email_campaign.png” />