Home

Awesome

Ahoy Email

First-party email analytics for Rails

:fire: For web and native app analytics, check out Ahoy

:bullettrain_side: To manage email subscriptions, check out Mailkick

Build Status

Installation

Add this line to your application’s Gemfile:

gem "ahoy_email"

Getting Started

There are three main features, which can be used independently:

Message History

To encrypt email addresses with Lockbox, install Lockbox and Blind Index and run:

rails generate ahoy:messages --encryption=lockbox
rails db:migrate

To use Active Record encryption (Rails 7+, experimental), run:

rails generate ahoy:messages --encryption=activerecord
rails db:migrate

If you prefer not to encrypt data, run:

rails generate ahoy:messages --encryption=none
rails db:migrate

Then, add to mailers:

class CouponMailer < ApplicationMailer
  has_history
end

Use the Ahoy::Message model to query messages:

Ahoy::Message.last

Use only and except to limit actions

class CouponMailer < ApplicationMailer
  has_history only: [:welcome]
end

To store history for all mailers, create config/initializers/ahoy_email.rb with:

AhoyEmail.default_options[:message] = true

Users

By default, Ahoy Email tries @user then params[:user] then User.find_by(email: message.to) to find the user. You can pass a specific user with:

class CouponMailer < ApplicationMailer
  has_history user: -> { params[:some_user] }
end

The user association is polymorphic, so use it with any model.

To get all messages sent to a user, add an association:

class User < ApplicationRecord
  has_many :messages, class_name: "Ahoy::Message", as: :user
end

And run:

user.messages

Extra Data

Add extra data to messages. Create a migration like:

class AddCouponIdToAhoyMessages < ActiveRecord::Migration[7.2]
  def change
    add_column :ahoy_messages, :coupon_id, :integer
  end
end

And use:

class CouponMailer < ApplicationMailer
  has_history extra: {coupon_id: 1}
end

You can use a proc as well.

class CouponMailer < ApplicationMailer
  has_history extra: -> { {coupon_id: params[:coupon].id} }
end

Options

Set global options

AhoyEmail.default_options[:user] = -> { params[:admin] }

Use a different model

AhoyEmail.message_model = -> { UserMessage }

Or fully customize how messages are tracked

AhoyEmail.track_method = lambda do |data|
  # your code
end

Data Retention

Delete older data with:

Ahoy::Message.where("sent_at < ?", 1.year.ago).in_batches.delete_all

Delete data for a specific user with:

Ahoy::Message.where(user_id: 1, user_type: "User").in_batches.delete_all

UTM Tagging

Use UTM tagging to attribute visits or conversions to an email campaign. Add UTM parameters to links with:

class CouponMailer < ApplicationMailer
  utm_params
end

The defaults are:

You can customize them with:

class CouponMailer < ApplicationMailer
  utm_params utm_campaign: -> { "coupon#{params[:coupon].id}" }
end

Use only and except to limit actions

class CouponMailer < ApplicationMailer
  utm_params only: [:welcome]
end

Skip specific links with:

<%= link_to "Go", some_url, data: {skip_utm_params: true} %>

Click Analytics

You can track click-through rate to see how well campaigns are performing. Stats can be stored in your database, Redis, or any other data store.

Database

Run:

rails generate ahoy:clicks
rails db:migrate

And create config/initializers/ahoy_email.rb with:

AhoyEmail.subscribers << AhoyEmail::DatabaseSubscriber
AhoyEmail.api = true

Redis

Add this line to your application’s Gemfile:

gem "redis"

And create config/initializers/ahoy_email.rb with:

# pass your Redis client if you already have one
AhoyEmail.subscribers << AhoyEmail::RedisSubscriber.new(redis: Redis.new)
AhoyEmail.api = true

Other

Create config/initializers/ahoy_email.rb with:

class EmailSubscriber
  def track_send(data)
    # your code
  end

  def track_click(data)
    # your code
  end

  def stats(campaign)
    # optional, for AhoyEmail.stats
  end
end

AhoyEmail.subscribers << EmailSubscriber
AhoyEmail.api = true

Usage

Add to mailers you want to track

class CouponMailer < ApplicationMailer
  track_clicks campaign: "my-campaign"
end

If storing stats in the database, the mailer should also use has_history

Use only and except to limit actions

class CouponMailer < ApplicationMailer
  track_clicks campaign: "my-campaign", only: [:welcome]
end

Or make it conditional

class CouponMailer < ApplicationMailer
  track_clicks campaign: "my-campaign", if: -> { params[:user].opted_in? }
end

You can also use a proc

class CouponMailer < ApplicationMailer
  track_clicks campaign: -> { "coupon-#{action_name}" }
end

Skip specific links with:

<%= link_to "Go", some_url, data: {skip_click: true} %>

By default, unsubscribe links are excluded. To change this, use:

AhoyEmail.default_options[:unsubscribe_links] = true

You can specify the domain to use with:

AhoyEmail.default_options[:url_options] = {host: "mydomain.com"}

Stats

Get stats for a campaign

AhoyEmail.stats("my-campaign")

History

View the changelog

Contributing

Everyone is encouraged to help improve this project. Here are a few ways you can help:

To get started with development:

git clone https://github.com/ankane/ahoy_email.git
cd ahoy_email
bundle install
bundle exec rake test