Giter VIP home page Giter VIP logo

mailkick's Introduction

Mailkick

Email subscriptions for Rails

  • Add one-click unsubscribe links to your emails
  • Fetch bounces and spam reports from your email service

📮 Check out Ahoy Email for analytics

Build Status

Installation

Add this line to your application’s Gemfile:

gem "mailkick"

And run the generator. This creates a table to store subscriptions.

bundle install
rails generate mailkick:install
rails db:migrate

Getting Started

Add has_subscriptions to your user model:

class User < ApplicationRecord
  has_subscriptions
end

Subscribe to a list

user.subscribe("sales")

Unsubscribe from a list

user.unsubscribe("sales")

Check if subscribed

user.subscribed?("sales")

Get subscribers for a list (use this for sending emails)

User.subscribed("sales")

Unsubscribe Links

Add an unsubscribe link to your emails. For HTML emails, use:

<%= link_to "Unsubscribe", mailkick_unsubscribe_url(@user, "sales") %>

For text emails, use:

Unsubscribe: <%= mailkick_unsubscribe_url(@user, "sales") %>

When a user unsubscribes, they are taken to a mobile-friendly page and given the option to resubscribe. To customize the view, run:

rails generate mailkick:views

which copies the view into app/views/mailkick.

Bounces and Spam Reports

Fetch bounces, spam reports, and unsubscribes from your email service. Create config/initializers/mailkick.rb with a method to handle opt outs.

Mailkick.process_opt_outs_method = lambda do |opt_outs|
  emails = opt_outs.map { |v| v[:email] }
  subscribers = User.includes(:mailkick_subscriptions).where(email: emails).index_by(&:email)

  opt_outs.each do |opt_out|
    subscriber = subscribers[opt_out[:email]]
    next unless subscriber

    subscriber.mailkick_subscriptions.each do |subscription|
      subscription.destroy if subscription.created_at < opt_out[:time]
    end
  end
end

And run:

Mailkick.fetch_opt_outs

The following services are supported:

Will gladly accept pull requests for others.

AWS SES

Add the gem

gem "aws-sdk-sesv2"

And configure your AWS credentials. Requires ses:ListSuppressedDestinations permission.

If you started using Amazon SES before November 25, 2019, you have to manually enable account-level suppression list feature.

Mailchimp

Add the gem

gem "gibbon", ">= 2"

And set ENV["MAILCHIMP_API_KEY"] and ENV["MAILCHIMP_LIST_ID"].

Mailgun

Add the gem

gem "mailgun-ruby"

And set ENV["MAILGUN_API_KEY"].

Mandrill

Add the gem

gem "mandrill-api"

And set ENV["MANDRILL_APIKEY"].

Postmark

Add the gem

gem "postmark"

And set ENV["POSTMARK_API_KEY"].

SendGrid

Add the gem

gem "sendgrid-ruby"

And set ENV["SENDGRID_API_KEY"]. The API key requires only the Suppressions permission.

Advanced

For more control over services, set them by hand.

Mailkick.services = [
  Mailkick::Service::SendGridV2.new(api_key: "API_KEY"),
  Mailkick::Service::Mailchimp.new(api_key: "API_KEY", list_id: "LIST_ID")
]

Reference

Access the subscription model directly

Mailkick::Subscription.all

Upgrading

1.0

Mailkick 1.0 stores subscriptions instead of opt-outs. To migrate:

  1. Add a table to store subscriptions
rails generate mailkick:install
rails db:migrate
  1. Change the following methods in your code:
  • mailkick_user to has_subscriptions
  • User.not_opted_out to User.subscribed(list)
  • opt_in to subscribe(list)
  • opt_out to unsubscribe(list)
  • opted_out? to !subscribed?(list)
  1. Add a user and list to mailkick_unsubscribe_url
mailkick_unsubscribe_url(user, list)
  1. Migrate data for each of your lists
opted_out_emails = Mailkick::Legacy.opted_out_emails(list: nil)
opted_out_users = Mailkick::Legacy.opted_out_users(list: nil)

User.find_in_batches do |users|
  users.reject! { |u| opted_out_emails.include?(u.email) }
  users.reject! { |u| opted_out_users.include?(u) }

  now = Time.now
  records =
    users.map do |user|
      {
        subscriber_type: user.class.name,
        subscriber_id: user.id,
        list: "sales",
        created_at: now,
        updated_at: now
      }
    end

  # use create! for Active Record < 6
  Mailkick::Subscription.insert_all!(records)
end
  1. Drop the mailkick_opt_outs table
drop_table :mailkick_opt_outs

Also, if you use Mailkick.fetch_opt_outs, add a method to handle opt outs.

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 and testing:

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

mailkick's People

Contributors

ankane avatar atul9 avatar barnett avatar cover avatar dlackty avatar edwinwills avatar keeruline avatar killion avatar manojmj92 avatar ryanharkins avatar theianjones avatar tmckd avatar vicramon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mailkick's Issues

Add ability to add / remove subscriptions with a single method call / query

Hey Andrew, now that Mailkick works as opt-in it requires to call subscribe multiple times, for example when a new user is created and it needs to be added to different kind of notifications (/lists).

What do you think about changing the subscribe to support an array of lists (with upsert_all maybe?)? That way someone could have something like after_create_commit { subscribe('list1', 'list2', 'list3') }.

unsubscribe should already work, maybe it could be worth to make it clear in the documentation that unsubscribe(['list1', 'list2']) works as well.

[Rails 6] Advice for injecting manual connection switching for unsubscribe GET route

https://edgeguides.rubyonrails.org/active_record_multiple_databases.html#using-manual-connection-switching

Using Rails 6 multiple database support, the GET request attempts to use the reader role which then fails. Thinking about what the cleanest approach to wrapping opt_out(options) inside of a ActiveRecord::Base.connected_to(role: :writing) block. Right now i'm just monkey patching the controller action.

Similar issues in other gems heartcombo/devise#5133

email should be base64 encoded, or MessageVerifier barfs on some characters

For example with the email '[email protected]', the '+' character causes a problem.

You can fix with this in processor.rb:
# email must be base64 encoded, e.g., for '+' character, or MessageVerifier barfs
encoded_email = Base64.encode64(email)
token = verifier.generate([encoded_email, user.try(:id), user.try(:class).try(:name), list])

and this in subscriptions_controller.rb:
# email must be base64 encoded, e.g., for '+' character, or MessageVerifier barfs
encoded_email, user_id, user_type, @list = verifier.verify(params[:id])
@email = Base64.decode64(encoded_email)

ActiveSupport::MessageVerifier::InvalidSignature

Currently, there is an issue when this exception is raised, it continues to render the actual view, and it breaks the app. It's because @options is nil when called oped_out? from the view.
It should halt the execution, however.

screen shot 2018-04-16 at 10 35 53 am

https://github.com/ankane/mailkick/blob/master/app/controllers/mailkick/subscriptions_controller.rb#L35
I see you already rescue this exception with the below code. However, it doesn't work for me on my rails app 5.1. (It works on rails 5.0.x)

render text: "Subscription not found", status: :bad_request

If I changed it to the below code, it works well.

render file: 'public/404.html', layout: false, status: 404

@ankane, Any thoughts?

Add endpoint to process bounces/spam in real time

Hey Andrew, it's me again 😁

Correct me if I'm wrong, at the moment the only way to sync bounces/spam email address and unsubscribe them in the app is through the Mailkick.fetch_opt_outs, which I guess it needs to be called in a cron on a daily/hourly basis. Is that right?

My worry about this is that with a big number of users there is also a risk of having many addresses to sync every time, and most of them would already be synced from the previous run.

Given that all services support a webhook/notification system to send in real time a request with the bounced/spam/unsubscribed addresses, have thought about adding an endpoint to process this requests and rely less on the fetch_opt_outs after the first run?

Support on POST on routes for new Google guidelines?

Google recently published new guidelines that will start to be enforced in February: https://support.google.com/mail/answer/81126

One of which is "Make it easy to unsubscribe":

Always give your recipients an easy way to unsubscribe from your messages. Letting people opt out of your messages can improve open rates, click-through rates, and sending efficiency. One-click unsubscribe makes it easy for people to opt out. If you send more than 5,000 message per day, your marketing and subscribed messages must support one-click unsubscribe.

To set up one-click unsubscribe, include both of these headers in outgoing messages:

List-Unsubscribe-Post: List-Unsubscribe=One-Click
List-Unsubscribe: https://solarmora.com/unsubscribe/example

When a recipient unsubscribes using one-click, you receive this POST request:

"POST /unsubscribe/example HTTP/1.1
Host: solarmora.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 26
List-Unsubscribe=One-Click"

At the moment Mailkick only supports GET requests on the routes.

From my understanding it should also not redirect:

The mail sender MUST NOT return an HTTPS redirect, since redirected
POST actions have historically not worked reliably, and many browsers
have turned redirected HTTP POSTs into GETs.

generate token method should reference base class to avoid STI child class mismatch

I think that the base_class should be referenced when generating the token since this is what is saved in the subscriber_type column:

module Mailkick
...
  def self.generate_token(subscriber, list)
    raise ArgumentError, "Missing subscriber" unless subscriber
    raise ArgumentError, "Missing list" unless list.present?

    message_verifier.generate([nil, subscriber.id, subscriber.class.base_class.name, list])
  end
...
end

otherwise the child class name is referenced and the subscriber, being of a child class type, can't unsubscribe.

preventing emails from getting sent

Is the gem intended to work with ActionMailer to prevent emails from being sent, or are we meant to wrap relevant calls in conditionals to prevent them from being sent?

I've added the gem to an app and have followed the instructions provided in the readme. The db has the mailkick table; the views are present, etc. The unsubscribe link appears in emails and I am able to click it, visit the unsubscribe page, and unsubscribe. The 'active' attribute switches to true. Finally, I have 'mailkick_user' added to my User model. However, the app still sends emails. There is no difference between pre gem installation and post, save for a new table and new views.

Mounting Mailkick::Engine

Rails.application.routes.draw do
  mount Mailkick::Engine => "/mailkick"
end

is happening from within the gem's config/routes.rb file. Seems like an anti-pattern considering your other projects (Blazer, Ahoy). Also, this doesn't work for me. Had to vendor the gem and move it to my route.rb

Different Model Name that user

Hey

I don't have a user model, instead i have a person model, could you please confirm how can i use
mailkick_user ?

Thanks in advance

Trying to customize mailkick view

I'm trying to customize the view generated by this command - "rails generate mailkick:views"
This command adds only view file(show.html.erb). Can you update the gem to generate the controller also? so I can use the layout of my own.

clicking on unsubscribe is leading to 404

model

class User < ApplicationRecord
  after_create :foo
  mailkick_user

  def foo
    UserMailer.welcome_email(self).deliver_now
  end
end

mailer

class UserMailer < ApplicationMailer
  default from: '[email protected]'
 
  def welcome_email(user)
    @user = user
    mail(to: '[email protected]', subject: 'Welcome to My Awesome Site')
  end
end

mailer view

Hi hello

 <%= link_to "unsubscribe", mailkick_unsubscribe_url(user: @user) %>

I am getting 404, after clicking the unsubscribe

Screen Shot 2019-08-28 at 4 26 00 PM

created a sample app here to demonstrate this easily

https://github.com/vamsipavanmahesh/check-mails

Inline Attachments prevent MAILKICK_TOKEN from being replaced

If you have an inline attachment in your mailer method, the Mailkick Processor only gets the inline attachment part, not the actual email text and/or email html part, and therefore sends the mailkick_unsubscribe_url out with MAILKICK_TOKEN in it.

I added attachments.inline['image.jpg'] = File.read('/path/to/image.jpg') in my mailer method and then image_tag attachments['image.jpg'].url in the mailer view.

I'm using ahoy_email.

#mailkick/lib/mailkick/processor.rb L21
message.parts.size
=> 2
message.parts.first.body.raw_source
=> {{the long binary image stuff}}
message.parts.last.body.raw_source
=> ""

I just removed my inline attachments, and that fixed it.

I have a feeling it's something to do with this:

message = super

Support for Lockbox?

Any plans to support lockbox encryption of the email column in the mailkick_opt_outs table?

how i can set mounted path for engine

issue - i have subscriptions path in rails application already. when i add mailkick it is mounted like

Routes for Mailkick::Engine:
unsubscribe_subscription GET  /subscriptions/:id/unsubscribe(.:format) mailkick/subscriptions#unsubscribe
  subscribe_subscription GET  /subscriptions/:id/subscribe(.:format)   mailkick/subscriptions#subscribe
            subscription GET  /subscriptions/:id(.:format)             mailkick/subscriptions#show

i look at https://github.com/ankane/mailkick/blob/master/config/routes.rb and i want mount to be in different path. example GET /mailkick_subscriptions/:id/unsubscribe(.:format)

how i can make this work? i tried config/initializers/mailkick.rb - Mailkick.mount = false and config/routes.rb - mount Mailkick::Engine, at: "/mailkick_subscriptions" but nothing work.

please help

How can we specify domain for a unsubscribe URL?

Actually, the backend of my app is deployed to heroku. So when I send an email with the unsubscribe link, after clicking on the link, I am redirected to localhost:3000. So How can I redirect the user to fixed url or specify domain for the unsubscribe url?

Multiple models?

Is there a way to use mail kick with multiple models other than "User"?

After unsubscribe/resubscribe event?

Is there a way to accomplish adding listeners and/or overriding the controller to perform something (ie. also unsubscribe/resub to mailchimp) after the controller processes the request?

If there isn't -- what is the best way to go about doing this?

Thanks in advance!

Path to manage subscriptions

What is the path for a user to manage their subscription?

I tried using mailkick_unsubscribe_url but get the error:

undefined method `mailkick_unsubscribe_url'

I also tried "subscribe_subscription_path" which is mentioned when I run rake routes.

Support for Opt-in by default?

Hi,

We are using mailkick in our Rails app to great effect and sound it to be a simple tool to add to our arsenal. However, with some recent privacy changes around the world but primarily here in New Zealand we now have the need to create an opt-in by default list, as we need to user's express permission first.

While we can reverse the logic in our code to mean the existence of an Mailkick::OptOut means they are actually opt-ed in, it's going to get really confusing really quickly, with high chance of accidental error, especially in production in the heat of the moment.

Therefore, looking at how "simple" the gem in (I mean no disrespect by that at all) we are considering duplicating some of the functionality:

  • creating a mailkick_opt_ins database table
  • adding a opted_in scope
  • adding a opted_in? method

Do you have any advice or plans to add support for this in the future? If you did a first-party solution you probably won't need 2 tables but we're wanting to keep things separate on purpose so we don't trample on each other's feet!

Thanks for all the hard work on myriad of gems, we use a lot of them and plan on using more in the future.

Thanks,
Dave

Deploying via Dockerfile results in a `raise ArgumentError, "Secret should not be nil." unless secret` exception

When deploying Mailkick to a Dockerfile, the following error occurs:

Step 12/23 : RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
 ---> Running in af9a7e2374ac
bin/rails aborted!
ArgumentError: Secret should not be nil. (ArgumentError)

      raise ArgumentError, "Secret should not be nil." unless secret
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
/rails/vendor/bundle/ruby/3.2.0/gems/activesupport-7.1.1/lib/active_support/message_verifier.rb:154:in `initialize'
/rails/vendor/bundle/ruby/3.2.0/gems/activesupport-7.1.1/lib/active_support/messages/rotator.rb:7:in `initialize'
/rails/vendor/bundle/ruby/3.2.0/gems/activesupport-7.1.1/lib/active_support/messages/rotator.rb:45:in `new'
/rails/vendor/bundle/ruby/3.2.0/gems/activesupport-7.1.1/lib/active_support/messages/rotator.rb:45:in `build_rotation'
/rails/vendor/bundle/ruby/3.2.0/gems/activesupport-7.1.1/lib/active_support/messages/rotator.rb:15:in `rotate'
/rails/vendor/bundle/ruby/3.2.0/gems/mailkick-1.2.1/lib/mailkick/engine.rb:23:in `block in <class:Engine>'
/rails/vendor/bundle/ruby/3.2.0/gems/railties-7.1.1/lib/rails/initializable.rb:32:in `instance_exec'
/rails/vendor/bundle/ruby/3.2.0/gems/railties-7.1.1/lib/rails/initializable.rb:32:in `run'
/rails/vendor/bundle/ruby/3.2.0/gems/railties-7.1.1/lib/rails/initializable.rb:61:in `block in run_initializers'
/rails/vendor/bundle/ruby/3.2.0/gems/railties-7.1.1/lib/rails/initializable.rb:60:in `run_initializers'
/rails/vendor/bundle/ruby/3.2.0/gems/railties-7.1.1/lib/rails/application.rb:423:in `initialize!'
/rails/config/environment.rb:5:in `<main>'

The mailkick initializer assumes it will get a secret key or base, but build environments don't have the secrets that are assumed to be present by the initializer.

To recreate, run the following on any machine with Docker:

rails new mail-app
bundle add mailkick
bundle add dockerfile-rails --optimistic --group development
bin/rails generate dockerfile 
docker build .

uninitialized constant Gibbon::API (NameError)

Trying to use this gem with Gibbon, and I get this error every time I try to load up the rails s or c. Thanks in advance.

/Users/austin/.rvm/gems/[email protected]/gems/mailkick-0.2.0/lib/mailkick/service/mailchimp.rb:7:in initialize': uninitialized constant Gibbon::API (NameError)`

Rails routes

I've installed the gem and added the "unsubscribe" link. However, I cannot find the mailkick route in routes.rb and in fact it's not rendered. It renders my homepage.

  1. How can I get the unsubscribe link for a specific user_id ? Like mailkick_unsubscribe_url(user_id: id) ?

/mailkick?locale=en/subscriptions/BAhbCUkiHWNocmlzdGluYUBpdHNudXRmYWlyLmNvbQY6BkVUaRBJIglVc2VyBjsARjA=--fe51a96fa69c038b472c76328d726898859befba/unsubscribe?locale=en

Mailkick.secret_token is nil in Rails 5.2 app w/ legacy config.secret_token

We haven't upgraded to config.secret_key_base yet (but we have an empty config/secrets.yml file); our Rails.application.config.secret_token is still stored in config/initializers/secret_token.rb.

When I tried to use Mailkick, I got an error about a missing Rails.application.config.secret_key_base. It looks like this line is evaluating to true, and then in this line, creds.respond_to?(:secret_key_base) evaluates to true when it should be false (it appears that creds.respond_to?(:anything) also evaluates to true).

Undefined method 'prepend'

I'm experiencing this error. The only thing I did add the gem to the bundle, install and when I run rake db:migrate --trace (or other tasks to test) I get:

rake aborted! NoMethodError: undefined methodprepend' for ActionMailer::Base:Class
/var/lib/gems/1.9.1/gems/actionmailer-4.2.5.1/lib/action_mailer/base.rb:569:in method_missing' /var/lib/gems/1.9.1/gems/mailkick-0.1.4/lib/mailkick.rb:90:in<top (required)>'
/home/marc/Documents/Programming/Code/ferlist/ferlistapi/config/application.rb:7:in <top (required)>' /home/marc/Documents/Programming/Code/ferlist/ferlistapi/Rakefile:4:in<top (required)>'
(See full trace by running task with --trace)
marc@ubuntu-marc:/Documents/Programming/Code/ferlist/ferlistapi$ rake db:migrate --trace
rake aborted!
NoMethodError: undefined method prepend' for ActionMailer::Base:Class /var/lib/gems/1.9.1/gems/actionmailer-4.2.5.1/lib/action_mailer/base.rb:569:inmethod_missing'
/var/lib/gems/1.9.1/gems/mailkick-0.1.4/lib/mailkick.rb:90:in <top (required)>' /usr/lib/ruby/vendor_ruby/bundler/runtime.rb:72:inrequire'
/usr/lib/ruby/vendor_ruby/bundler/runtime.rb:72:in block (2 levels) in require' /usr/lib/ruby/vendor_ruby/bundler/runtime.rb:70:ineach'
/usr/lib/ruby/vendor_ruby/bundler/runtime.rb:70:in block in require' /usr/lib/ruby/vendor_ruby/bundler/runtime.rb:59:ineach'
/usr/lib/ruby/vendor_ruby/bundler/runtime.rb:59:in require' /usr/lib/ruby/vendor_ruby/bundler.rb:132:inrequire'
/home/marc/Documents/Programming/Code/ferlist/ferlistapi/config/application.rb:7:in <top (required)>' /usr/lib/ruby/1.9.1/rubygems/custom_require.rb:36:inrequire'
/usr/lib/ruby/1.9.1/rubygems/custom_require.rb:36:in require' /home/marc/Documents/Programming/Code/ferlist/ferlistapi/Rakefile:4:in<top (required)>'
/var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/rake_module.rb:28:in load' /var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/rake_module.rb:28:inload_rakefile'
/var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/application.rb:689:in raw_load_rakefile' /var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/application.rb:94:inblock in load_rakefile'
/var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/application.rb:176:in standard_exception_handling' /var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/application.rb:93:inload_rakefile'
/var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/application.rb:77:in block in run' /var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/application.rb:176:instandard_exception_handling'
/var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/application.rb:75:in run' /var/lib/gems/1.9.1/gems/rake-10.5.0/bin/rake:33:in<top (required)>'
/usr/local/bin/rake:23:in load' /usr/local/bin/rake:23:in

'
marc@ubuntu-marc:/Documents/Programming/Code/ferlist/ferlistapi$ rake db:migrate --trace
rake aborted!
NoMethodError: undefined method prepend' for ActionMailer::Base:Class /var/lib/gems/1.9.1/gems/actionmailer-4.2.5.1/lib/action_mailer/base.rb:569:inmethod_missing'
/var/lib/gems/1.9.1/gems/mailkick-0.1.4/lib/mailkick.rb:90:in <top (required)>' /usr/lib/ruby/vendor_ruby/bundler/runtime.rb:72:inrequire'
/usr/lib/ruby/vendor_ruby/bundler/runtime.rb:72:in block (2 levels) in require' /usr/lib/ruby/vendor_ruby/bundler/runtime.rb:70:ineach'
/usr/lib/ruby/vendor_ruby/bundler/runtime.rb:70:in block in require' /usr/lib/ruby/vendor_ruby/bundler/runtime.rb:59:ineach'
/usr/lib/ruby/vendor_ruby/bundler/runtime.rb:59:in require' /usr/lib/ruby/vendor_ruby/bundler.rb:132:inrequire'
/home/marc/Documents/Programming/Code/ferlist/ferlistapi/config/application.rb:7:in <top (required)>' /usr/lib/ruby/1.9.1/rubygems/custom_require.rb:36:inrequire'
/usr/lib/ruby/1.9.1/rubygems/custom_require.rb:36:in require' /home/marc/Documents/Programming/Code/ferlist/ferlistapi/Rakefile:4:in<top (required)>'
/var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/rake_module.rb:28:in load' /var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/rake_module.rb:28:inload_rakefile'
/var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/application.rb:689:in raw_load_rakefile' /var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/application.rb:94:inblock in load_rakefile'
/var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/application.rb:176:in standard_exception_handling' /var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/application.rb:93:inload_rakefile'
/var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/application.rb:77:in block in run' /var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/application.rb:176:instandard_exception_handling'
/var/lib/gems/1.9.1/gems/rake-10.5.0/lib/rake/application.rb:75:in run' /var/lib/gems/1.9.1/gems/rake-10.5.0/bin/rake:33:in<top (required)>'
/usr/local/bin/rake:23:in load' /usr/local/bin/rake:23:in'
`
If I remove the gem and run the bundle again the error disappears. I'm using rails 4.2.5.1 and the gem rails-api.

Running rails generate mailkick:install outputs:

/var/lib/gems/1.9.1/gems/actionmailer-4.2.5.1/lib/action_mailer/base.rb:569:inmethod_missing': undefined method prepend' for ActionMailer::Base:Class (NoMethodError) from /var/lib/gems/1.9.1/gems/mailkick-0.1.4/lib/mailkick.rb:90:in<top (required)>'
from /usr/lib/ruby/vendor_ruby/bundler/runtime.rb:72:in require' from /usr/lib/ruby/vendor_ruby/bundler/runtime.rb:72:inblock (2 levels) in require'
from /usr/lib/ruby/vendor_ruby/bundler/runtime.rb:70:in each' from /usr/lib/ruby/vendor_ruby/bundler/runtime.rb:70:inblock in require'
from /usr/lib/ruby/vendor_ruby/bundler/runtime.rb:59:in each' from /usr/lib/ruby/vendor_ruby/bundler/runtime.rb:59:inrequire'
from /usr/lib/ruby/vendor_ruby/bundler.rb:132:in require' from /home/marc/Documents/Programming/Code/ferlist/ferlistapi/config/application.rb:7:in<top (required)>'
from /var/lib/gems/1.9.1/gems/railties-4.2.5.1/lib/rails/commands/commands_tasks.rb:141:in require' from /var/lib/gems/1.9.1/gems/railties-4.2.5.1/lib/rails/commands/commands_tasks.rb:141:inrequire_application_and_environment!'
from /var/lib/gems/1.9.1/gems/railties-4.2.5.1/lib/rails/commands/commands_tasks.rb:128:in generate_or_destroy' from /var/lib/gems/1.9.1/gems/railties-4.2.5.1/lib/rails/commands/commands_tasks.rb:50:ingenerate'
from /var/lib/gems/1.9.1/gems/railties-4.2.5.1/lib/rails/commands/commands_tasks.rb:39:in run_command!' from /var/lib/gems/1.9.1/gems/railties-4.2.5.1/lib/rails/commands.rb:17:in<top (required)>'
from bin/rails:9:in require' from bin/rails:9:in

'`

Which seems to be referring to the same prepend issue.

Subscription not found

When I click the unsubscribe link within a email, the web page says 'Subscription not found'. User is correctly unsubscribed, though

Secret should not be nil error on upgrading to 0.3.0

2018-04-21T05:20:34.713Z 1 TID-owuox7dqh WARN: ActionView::Template::Error: Secret should not be nil. 2018-04-21T05:20:34.713Z 1 TID-owuox7dqh WARN: /usr/local/bundle/gems/activesupport-5.2.0/lib/active_support/message_verifier.rb:107:in initialize'
/usr/local/bundle/gems/activesupport-5.2.0/lib/active_support/messages/rotator.rb:7:in initialize' /usr/local/bundle/gems/mailkick-0.3.0/lib/mailkick.rb:91:in new'
/usr/local/bundle/gems/mailkick-0.3.0/lib/mailkick.rb:91:in message_verifier' /usr/local/bundle/gems/mailkick-0.3.0/lib/mailkick.rb:96:in generate_token'
/usr/local/bundle/gems/mailkick-0.3.0/app/helpers/mailkick/url_helper.rb:9:in mailkick_unsubscribe_url' /app/app/views/layouts/mailer.html.erb:24:in _app_views_layouts_mailer_html_erb__2296802602023607961_46968723266240'`

Unclear how to send email through mailkick

In repo I found method mail_with_mailkick, but in readme nothing said about that. Does it automatically called for every sent email or you need to do that manually? What if I have marketing emails and simple emails like welcome email after sign up?

Why use CGI?

I am just curious, why do you use CGI to insert the token?

Thanks

Ideas

1.0

  • Switch to opt-in subscriptions (has_subscriptions)
  • Switch to keyword arguments
  • Drop support for Action Mailer < 5.2 and Ruby < 2.6

Ruby 2.3 + Rails 5 = RuntimeError: can't modify frozen String

Standard code to send email:

    mail(headers) do |format|
      format.html { render text: email_text }
    end
RuntimeError: can't modify frozen String
/home/nowaker/.rvm/gems/ruby-2.3.3/gems/mailkick-0.1.5/lib/mailkick/processor.rb:23:in `gsub!'
/home/nowaker/.rvm/gems/ruby-2.3.3/gems/mailkick-0.1.5/lib/mailkick/processor.rb:23:in `block in process'
/home/nowaker/.rvm/gems/ruby-2.3.3/gems/mailkick-0.1.5/lib/mailkick/processor.rb:22:in `each'
/home/nowaker/.rvm/gems/ruby-2.3.3/gems/mailkick-0.1.5/lib/mailkick/processor.rb:22:in `process'
/home/nowaker/.rvm/gems/ruby-2.3.3/gems/mailkick-0.1.5/lib/mailkick/mailer.rb:6:in `block in mail'
/home/nowaker/.rvm/gems/ruby-2.3.3/gems/safely_block-0.1.1/lib/safely/core.rb:40:in `safely'
/home/nowaker/.rvm/gems/ruby-2.3.3/gems/mailkick-0.1.5/lib/mailkick/mailer.rb:6:in `mail'
./engines/hosting/app/mailers/billing_mailer.rb:35:in `new_machine_on_existing_subscription'

Rails 5 has done a lot of optimizations in how strings are handled and this might be the reason why this fails.

AWS SES support

It'd be great if Mailkick would support AWS SES as a provider, it should be possible to fetch the data using their ruby client.

DEPRECATION WARNING: Initialization autoloaded the constant Mailkick::UrlHelper.

Hi @ankane

First for all thank you for the gem, I appreciate it. When running rails commands like rails console or rails db:migrate I'm getting deprecation warning. This might not be necessarily an issue with your gem. As soon as I remove your gem from Gemfile and run bundle then deprecation warning disappears. I'm also using the code below (as suggested here) in the initializer to detect from where the warning comes from. But I'm not getting any results. Any help appreciated.

ActiveSupport.on_load(:action_controller_base) do
  bc = ActiveSupport::BacktraceCleaner.new
  bc.remove_silencers!
  bc.add_silencer { |line| line.start_with?(RbConfig::CONFIG['rubylibdir']) }
  bc.add_silencer do |line|
    line =~ Regexp.union(
      *(
        %w[bootsnap railties spring activesupport actionpack zeitwerk thor rack]
        .map { |g| /\A#{g} \([\w.]+\) / }
      ),
      %r{\Abin/rails}
    )
  end
  trace = bc.clean(caller)
  puts "Cleaned backtrace:\n\t#{trace.join("\n\t")}\n"
  puts "Most probably the cause is: #{trace.first}"
  puts "If not - uncomment `raise` at #{__FILE__}:#{__LINE__ + 1}"
  # raise "i can haz full backtrace"
  exit(1)
end

Applying to non-mailkick objects

Our app has a PortalUser model that is a mailkick_user. We also have a User model that is not a mailkick_user.

When an email is sent to a portaluser, mailkick correctly uses the defined user_method in PortalUser to find the PortalUser from the db. It populates the link and does what it is supposed to do.

When an email is sent to anything else (whether a User or a hardcoded email address), mailkick is still trying to attach itself, and queries the User model by email address. Our User model doesn't have email address since it is LDAP auth through Devise. This fails and causes the email not to be sent.

Model instance method not using email_key

The email_key passed into mailkick_user is not used for the following methods.

Please kindly fix it. 🙏

# mailkick/lib/mailkick/model.rb

def opted_out?(options = {})
  Mailkick.opted_out?({email: email, user: self}.merge(options))
end

def opt_out(options = {})
  Mailkick.opt_out({email: email, user: self}.merge(options))
end

def opt_in(options = {})
  Mailkick.opt_in({email: email, user: self}.merge(options))
end

Sending email via bulk sendgrid (for example)

Looks like the mechanisms here don't work so well when we use the headers['X-SMTPAPI'] to send to hundreds of subscribers. The mailkick_unsubscribe_url is based on the original :to value in the mail method. I find myself adjusting the url helper to carry an additional token "-subscriberId-" in which send grid can resolve the values, so the url's back to the unsubscribe controller action have the real recipient's identity.

Is there a better / built-in way?

Support unsubscription through emails

Hi Andrew, have you thought about supporting unsubscriptions through emails?

I was reading about the List-Unsubscribe header, and it seems that it supports both urls (which would be already covered by Mailkick) and mailto, but it seems that urls are only supported by gmail, all other providers won't use them, so it'd be better supporting mailto unsubscriptions as well.

Given that Rails now has ActionMailbox that would abstract the processing of incoming emails I was thinking if Mailkick would start support them as well. It could generate an unsubscription address that would be signed & unique for each user (and list type), and then it'd process them by unsubscribing the user as usual when an email is received.

What do you think? That'd be super useful

header[:mailkick_list] does not work

In the README you have stated that to create an unsubscribe link for a particular list to simply assign variable header[:mailkick_list] with the desired list name. In looking at the docs, it looks like you may have updated that variable to message[:mailkick_list]. Just thought it'd be a small thing to fix in the readme for other folks.

Also, for folks that may not have the rails routes automatically included into the mailers, I'd add a bit of documentation letting folks know that the unsubscribe urls can easily be called in templates after adding this to ApplicationMailer (or whatever mailer you're using):

class ApplicationMailer < ActionMailer::Base
  ...
  include Rails.application.routes.url_helpers
  def self.default_url_options
    ActionMailer::Base.default_url_options
  end
  ...
end

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.