Moist

Drip engine, for Ruby on Rails.

Why the name?

Because drip was taken and moist was available.

There is also a measurable amount of people who hate the word moist. Which results in a poor adoption strategy. In turn, fewer stars. Conclusion: recruiters won't bother me?

Goals?

Should you use this?

Maybe, here's the gist:

# Create a campaign:
::Moist::Campaign.create(name: "Abandoned cart", slug: "cart")

# Add `moist` to mailer:
class AbandonedCartMailer < ActionMailer::Base
  moist :you_forgot_something, campaign: :cart, step: 1, delay: 1.hour 
  def you_forgot_something(cart)
    # ... 
  end

  moist :selling_out_soon, campaign: :cart, step: 2, delay: 4.hours 
  def selling_out_soon(cart)
    # ... 
  end
end 

# Add to a drip campaign:
::Moist::Campaign.subscribe(cart, user: cart.user).to(:cart)

# Remove from a drip campaign:
::Moist::Campaign.unsubscribe(cart, user: cart.user).from(:cart)

Installation

Add this line to your application's Gemfile:

gem 'moist'

And then execute:

$ bundle

Bring in the initializer and migrations:

$ rails g moist:setup

And do the migrate:

$ rails db:migrate 

Setup Moist

How to use Moist to create an abandoned cart drip campaign.

1. First, create a Moist::Campaign.

Moist::Campaign.create(name: "Abandoned cart, slug: "cart")`

The slug used here will be referenced later. Note it.

2. Use moist in your mailer to create steps.

You'll need to define @moist_subscriber and @moist_user in each mailer. This tells Moist what Moist::Mailing to associate with the mailer.

class AbandonedCartMailer < ActionMailer::Base
  moist :you_forgot_something, campaign: :cart, step: 1, delay: 1.hour 
  def you_forgot_something(cart)
    @cart = cart 
    @moist_subscriber = cart 
    @moist_user = cart.user 
    mail(subject: "You forgot something...", to: @cart.user.email)
  end

  moist :free_shipping_if_you_order_now, campaign: :cart, step: 2, delay: 2.days
  def free_shipping_if_you_order_now(cart)
    @cart = cart 
    @moist_subscriber = cart 
    @moist_user = cart.user 
    mail(subject: "Free shipping? Yep. Complete your purchase right meow!", to: @cart.user.email)
  end
end 

In these mailer methods, make sure you assign a @moist_subscriber and a @moist_user. If you don't, bad things will happen.

3. Add @cart to the campaign

Probably a background job.

class AbandonedCartJob < ApplicationJob
  TIME_TO_LIVE = 1.hour 
  def perform
    Cart.where(checked_out: false).where('updated_at < ?', TIME_TO_LIVE.ago).each do |cart|
      ::Moist::Campaign.subscribe(cart, user: cart.user).to(:cart)
    end 
  end 
end 

4. Run the scheduler

This manages the mailers and the drips.

class MoistSchedulerJob < ApplicationJob
  def perform
    ::Moist::Scheduler.run 
  end
end 

5. Done!

All done. Some nice-to-haves:

class Cart 
  has_moist_campaigns
end

Gives you

@cart.moist_campaigns
class User 
  acts_as_moist_user
end 

Gives you:

@user.moist_subscriptions

Data models

Three core concepts:

Campaign

This is just an object for reference.

CampaignSubscriber

This joins a Campaign to a subscriber (polymorphic anything), and a user. In theory, a user can have many moist_campaign_subscribers, which would relate it to objects that belong to a user.

This is useful if you have an Order object and a Subscription object that you want to create different campaigns for.

Mailing

Takes your ActionMailer moist calls and turns them into database records. They have send_at and sent_at columns, in addition to information about the mailers they belong to.

Contributing

Just do it.

Todo

  • Logo, duh
  • Web UI examples
  • Ability to pause a campaign?
  • Conversion stuff
  • Handle updating steps
  • Use block for handling delay

License

The gem is available as open source under the terms of the MIT License.