replyr 0.0.7 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/CHANGELOG.md +4 -0
 - data/README.md +41 -5
 - data/lib/generators/replyr/templates/mailman_server +3 -3
 - data/lib/generators/replyr/templates/replyr.rb.erb +12 -3
 - data/lib/replyr.rb +31 -4
 - data/lib/replyr/address_builder.rb +69 -0
 - data/lib/replyr/bounce_address.rb +52 -0
 - data/lib/replyr/config.rb +18 -5
 - data/lib/replyr/{reply_email.rb → email.rb} +20 -4
 - data/lib/replyr/engine.rb +1 -0
 - data/lib/replyr/handle_bounce.rb +35 -0
 - data/lib/replyr/reply_address.rb +7 -68
 - data/lib/replyr/version.rb +1 -1
 - data/test/dummy/app/models/user.rb +5 -0
 - data/test/dummy/config/initializers/replyr.rb +2 -1
 - data/test/dummy/db/test.sqlite3 +0 -0
 - data/test/dummy/log/test.log +16508 -0
 - data/test/replyr/bounce_address_test.rb +91 -0
 - data/test/replyr/email_test.rb +93 -0
 - data/test/replyr/emails/bounce_422.eml +98 -0
 - data/test/replyr/emails/bounce_530.eml +96 -0
 - data/test/replyr/handle_bounce_test.rb +33 -0
 - data/test/replyr/handle_reply_test.rb +1 -1
 - data/test/test_helper.rb +1 -1
 - metadata +11 -4
 - data/test/replyr/reply_email_test.rb +0 -51
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA1:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 09c0af70b27c7e54b91f2df46e1f397d57449109
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: b00e0b4df98e524e279235a580159b85b6714000
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: f2667bea0a35a65e7fde2635b636de4a99e33972e55425cd9be7f39835187add195525717296296cc54d630e4d55c394e511a9598a4a80dc3314937faba3f70a
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: ef21000d33e3b10ca64293290ca3699554c46d83f14653d4495e756bd8a78e689300bbc73ebae3b732f6216ffe0e0b26afa34da45d4cb767a1b6e2fda047868a
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | 
         @@ -25,7 +25,7 @@ https://github.com/titanous/mailman 
     | 
|
| 
       25 
25 
     | 
    
         
             
              - Ruby >= 1.9.3
         
     | 
| 
       26 
26 
     | 
    
         
             
              - Rails 3 >= 3.1 or Rails 4.x
         
     | 
| 
       27 
27 
     | 
    
         | 
| 
       28 
     | 
    
         
            -
            ##  
     | 
| 
      
 28 
     | 
    
         
            +
            ## Installation
         
     | 
| 
       29 
29 
     | 
    
         | 
| 
       30 
30 
     | 
    
         
             
            #### Add the gem to your `Gemfile`
         
     | 
| 
       31 
31 
     | 
    
         | 
| 
         @@ -45,7 +45,7 @@ $ rails g replyr:install 
     | 
|
| 
       45 
45 
     | 
    
         
             
            Open up `config/initializers/replyr.rb` and set the host name of your reply email address.
         
     | 
| 
       46 
46 
     | 
    
         | 
| 
       47 
47 
     | 
    
         
             
            ```ruby
         
     | 
| 
       48 
     | 
    
         
            -
            Ryplr.config. 
     | 
| 
      
 48 
     | 
    
         
            +
            Ryplr.config.reply_host = "yourdomain.com"
         
     | 
| 
       49 
49 
     | 
    
         
             
            ```
         
     | 
| 
       50 
50 
     | 
    
         | 
| 
       51 
51 
     | 
    
         
             
            #### Setup Mailman Gem
         
     | 
| 
         @@ -54,6 +54,8 @@ The Install Generator will already have created a `script/mailman_server` file w 
     | 
|
| 
       54 
54 
     | 
    
         | 
| 
       55 
55 
     | 
    
         
             
            ## Usage
         
     | 
| 
       56 
56 
     | 
    
         | 
| 
      
 57 
     | 
    
         
            +
            ### Reply Handling
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
       57 
59 
     | 
    
         
             
            #### Make a model accept replies
         
     | 
| 
       58 
60 
     | 
    
         | 
| 
       59 
61 
     | 
    
         
             
            Update your ActiveRecord models you want to reply to by adding a `handle_reply` like this:
         
     | 
| 
         @@ -65,6 +67,7 @@ class Comment < ActiveRecord::Base 
     | 
|
| 
       65 
67 
     | 
    
         
             
              handle_reply do |comment, user, text, files|
         
     | 
| 
       66 
68 
     | 
    
         
             
                Comment.create(body: text)
         
     | 
| 
       67 
69 
     | 
    
         
             
              end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
       68 
71 
     | 
    
         
             
            end
         
     | 
| 
       69 
72 
     | 
    
         
             
            ```
         
     | 
| 
       70 
73 
     | 
    
         | 
| 
         @@ -78,15 +81,48 @@ To add the unique reply address to your outgoing emails and make them 'replyable 
     | 
|
| 
       78 
81 
     | 
    
         | 
| 
       79 
82 
     | 
    
         
             
            ```ruby
         
     | 
| 
       80 
83 
     | 
    
         
             
            class CommentMailer < ActionMailer::Base
         
     | 
| 
      
 84 
     | 
    
         
            +
             
     | 
| 
       81 
85 
     | 
    
         
             
              def new_comment(user, comment)
         
     | 
| 
       82 
     | 
    
         
            -
                mail 
     | 
| 
      
 86 
     | 
    
         
            +
                mail to: user.email, reply_to: comment.reply_address_for_user(user)
         
     | 
| 
       83 
87 
     | 
    
         
             
              end
         
     | 
| 
      
 88 
     | 
    
         
            +
              
         
     | 
| 
       84 
89 
     | 
    
         
             
            end
         
     | 
| 
       85 
90 
     | 
    
         
             
            ```
         
     | 
| 
       86 
91 
     | 
    
         | 
| 
       87 
     | 
    
         
            -
             
     | 
| 
      
 92 
     | 
    
         
            +
            ### Bounce Handling
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
            #### Make a model accept bounce emails
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
            Add a `handle_bounce` call to the ActiveRecord model you want to handle your bounce emails on.
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 99 
     | 
    
         
            +
            class User < ActiveRecord::Base
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
              handle_bounce do |user, email|
         
     | 
| 
      
 102 
     | 
    
         
            +
                # Put your custom bounce handling code here
         
     | 
| 
      
 103 
     | 
    
         
            +
                # e.g. mark email as invalid
         
     | 
| 
      
 104 
     | 
    
         
            +
                user.update_attribute(:email_valid, false)
         
     | 
| 
      
 105 
     | 
    
         
            +
              end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
            end
         
     | 
| 
      
 108 
     | 
    
         
            +
            ```
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
            #### Update your mailers and set the `return_path`
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 113 
     | 
    
         
            +
            class CommentMailer < ActionMailer::Base
         
     | 
| 
      
 114 
     | 
    
         
            +
              
         
     | 
| 
      
 115 
     | 
    
         
            +
              def new_comment(user, comment)
         
     | 
| 
      
 116 
     | 
    
         
            +
                mail to: user.email, return_path: user.bounce_address
         
     | 
| 
      
 117 
     | 
    
         
            +
              end
         
     | 
| 
      
 118 
     | 
    
         
            +
              
         
     | 
| 
      
 119 
     | 
    
         
            +
            end
         
     | 
| 
      
 120 
     | 
    
         
            +
            ```
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
            ## Start up the worker
         
     | 
| 
       88 
124 
     | 
    
         | 
| 
       89 
     | 
    
         
            -
            Start/Stop  
     | 
| 
      
 125 
     | 
    
         
            +
            Start/Stop the Mailman background worker with the following commands:
         
     | 
| 
       90 
126 
     | 
    
         | 
| 
       91 
127 
     | 
    
         
             
            ```bash
         
     | 
| 
       92 
128 
     | 
    
         
             
            $ RAILS_ENV=production script/mailman_daemon start
         
     | 
| 
         @@ -34,11 +34,11 @@ Mailman.config.maildir = "~/Maildir" 
     | 
|
| 
       34 
34 
     | 
    
         
             
            Mailman::Application.run do  
         
     | 
| 
       35 
35 
     | 
    
         | 
| 
       36 
36 
     | 
    
         
             
              # DO NOT CHANGE. Replyr Route for processing reply emails
         
     | 
| 
       37 
     | 
    
         
            -
              to Replyr. 
     | 
| 
       38 
     | 
    
         
            -
                Replyr 
     | 
| 
      
 37 
     | 
    
         
            +
              to Replyr.route do
         
     | 
| 
      
 38 
     | 
    
         
            +
                Replyr.process(message)
         
     | 
| 
       39 
39 
     | 
    
         
             
              end
         
     | 
| 
       40 
40 
     | 
    
         | 
| 
       41 
     | 
    
         
            -
              # add your custom routes (independent of Replyr) here
         
     | 
| 
      
 41 
     | 
    
         
            +
              # add your custom mailman routes (independent of Replyr) here
         
     | 
| 
       42 
42 
     | 
    
         
             
              # to 'support%@example.org' do
         
     | 
| 
       43 
43 
     | 
    
         
             
              #   # ...
         
     | 
| 
       44 
44 
     | 
    
         
             
              # end
         
     | 
| 
         @@ -1,8 +1,17 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # Configuration for Replyr
         
     | 
| 
       2 
2 
     | 
    
         
             
            #
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
       4 
     | 
    
         
            -
            # Set the host name for your reply email addresses
         
     | 
| 
       5 
     | 
    
         
            -
            Replyr.config.host = "example.com"
         
     | 
| 
       6 
     | 
    
         
            -
             
     | 
| 
       7 
4 
     | 
    
         
             
            # Set a secure secret token to be used for salting the email-address 
         
     | 
| 
       8 
5 
     | 
    
         
             
            Replyr.config.secret = "<%= defined?(SecureRandom) ? SecureRandom.hex(32) : ActiveSupport::SecureRandom.hex(32) %>"
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            # Set the host name for your reply email addresses
         
     | 
| 
      
 8 
     | 
    
         
            +
            Replyr.config.reply_host = "example.com"
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            # Set the host name for your bounce email addresses
         
     | 
| 
      
 11 
     | 
    
         
            +
            Replyr.config.bounce_host = "example.com"
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            # Set custom prefix for your reply email addresses (default: "reply")
         
     | 
| 
      
 14 
     | 
    
         
            +
            # Replyr.config.reply_prefix = "reply"
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            # Set custom prefix for your bounce email addresses (default: "bounce")
         
     | 
| 
      
 17 
     | 
    
         
            +
            # Replyr.config.bounce_prefix = "bounce"
         
     | 
    
        data/lib/replyr.rb
    CHANGED
    
    | 
         @@ -1,9 +1,15 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require 'mailman'
         
     | 
| 
       2 
2 
     | 
    
         
             
            require 'email_reply_parser/email_reply_parser'
         
     | 
| 
       3 
3 
     | 
    
         
             
            require "replyr/config"
         
     | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            require "replyr/address_builder"
         
     | 
| 
       5 
6 
     | 
    
         
             
            require "replyr/reply_address"
         
     | 
| 
      
 7 
     | 
    
         
            +
            require "replyr/bounce_address"
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            require "replyr/email"
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
       6 
11 
     | 
    
         
             
            require "replyr/handle_reply"
         
     | 
| 
      
 12 
     | 
    
         
            +
            require "replyr/handle_bounce"
         
     | 
| 
       7 
13 
     | 
    
         
             
            require 'replyr/engine'
         
     | 
| 
       8 
14 
     | 
    
         | 
| 
       9 
15 
     | 
    
         
             
            # Monkey Patch broken listen dependency in mailman v0.7.0
         
     | 
| 
         @@ -21,10 +27,31 @@ module Replyr 
     | 
|
| 
       21 
27 
     | 
    
         
             
                  @logger = (defined?(Rails) && Rails.logger) ? Rails.logger : Logger.new(STDOUT)
         
     | 
| 
       22 
28 
     | 
    
         
             
                end
         
     | 
| 
       23 
29 
     | 
    
         | 
| 
       24 
     | 
    
         
            -
                 
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
      
 30 
     | 
    
         
            +
                # Regexp for reply addresses:
         
     | 
| 
      
 31 
     | 
    
         
            +
                # reply-comment-12-56-01ce26dc69094af9246ea7e7ce9970aff2b81cc9@reply.example.com
         
     | 
| 
      
 32 
     | 
    
         
            +
                #
         
     | 
| 
      
 33 
     | 
    
         
            +
                def reply_pattern
         
     | 
| 
      
 34 
     | 
    
         
            +
                  /#{config.reply_prefix}-(?<model_name>[a-z,#]+)-(?<model_id>\d+)-(?<user_id>\d+)-(?<token>\S+)@#{config.reply_host}/
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
                
         
     | 
| 
      
 37 
     | 
    
         
            +
                # Regexp for bounce addresses:
         
     | 
| 
      
 38 
     | 
    
         
            +
                # bounce-newsletter-12-01ce26dc69094af9246ea7e7ce9970aff2b81cc9@bounce.example.com
         
     | 
| 
      
 39 
     | 
    
         
            +
                #
         
     | 
| 
      
 40 
     | 
    
         
            +
                def bounce_pattern
         
     | 
| 
      
 41 
     | 
    
         
            +
                  /#{config.bounce_prefix}-(?<model_name>[a-z,#]+)-(?<model_id>\d+)-(?<token>\S+)@#{config.bounce_host}/
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
                
         
     | 
| 
      
 44 
     | 
    
         
            +
                # Regexp for bounce and reply addresses.
         
     | 
| 
      
 45 
     | 
    
         
            +
                # Use this as the Replyr route in your mailman-server.
         
     | 
| 
      
 46 
     | 
    
         
            +
                #
         
     | 
| 
      
 47 
     | 
    
         
            +
                def route
         
     | 
| 
      
 48 
     | 
    
         
            +
                  /#{reply_pattern}|#{bounce_pattern}/
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
                alias_method :address_pattern, :route
         
     | 
| 
      
 51 
     | 
    
         
            +
                
         
     | 
| 
      
 52 
     | 
    
         
            +
                def process(message)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  Replyr::Email.process(message)
         
     | 
| 
       26 
54 
     | 
    
         
             
                end
         
     | 
| 
       27 
     | 
    
         
            -
                alias_method :mailman_route, :address_pattern
         
     | 
| 
       28 
55 
     | 
    
         | 
| 
       29 
56 
     | 
    
         
             
              end
         
     | 
| 
       30 
57 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,69 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'openssl'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Replyr
         
     | 
| 
      
 4 
     | 
    
         
            +
              module AddressBuilder
         
     | 
| 
      
 5 
     | 
    
         
            +
                # Model name may be namespaced (e.g. MyApp::Comment)
         
     | 
| 
      
 6 
     | 
    
         
            +
                # For the reply email address the model name will be converted to "my_app/comment".
         
     | 
| 
      
 7 
     | 
    
         
            +
                # Then the slash "/" will be replaced with a plus sign "+", because
         
     | 
| 
      
 8 
     | 
    
         
            +
                # slashes should not be used email addresses.
         
     | 
| 
      
 9 
     | 
    
         
            +
                #
         
     | 
| 
      
 10 
     | 
    
         
            +
                def normalized_model_name(model)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  model.class.name.tableize.singularize.gsub("/", "+")
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
                
         
     | 
| 
      
 14 
     | 
    
         
            +
                # Converts a normalized model_name back to a real model name
         
     | 
| 
      
 15 
     | 
    
         
            +
                # and returns the class
         
     | 
| 
      
 16 
     | 
    
         
            +
                #
         
     | 
| 
      
 17 
     | 
    
         
            +
                def self.class_from_normalized_model_name(model_name)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  model_name.gsub("+", "/").classify.constantize
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
                
         
     | 
| 
      
 21 
     | 
    
         
            +
                # Returns the ID of an AR object.
         
     | 
| 
      
 22 
     | 
    
         
            +
                # Uses primary key to find out the correct field
         
     | 
| 
      
 23 
     | 
    
         
            +
                #
         
     | 
| 
      
 24 
     | 
    
         
            +
                def id_from_model(object)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  object.send(object.class.primary_key)
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
                
         
     | 
| 
      
 28 
     | 
    
         
            +
                # Split the reply/bounce email address. It has the following format:
         
     | 
| 
      
 29 
     | 
    
         
            +
                # Reply: reply-comment-12-56-01ce26dc69094af9246ea7e7ce9970aff2b81cc9@reply.example.com
         
     | 
| 
      
 30 
     | 
    
         
            +
                # Bounce: bounce-newsletter-12-01ce26dc69094af9246ea7e7ce9970aff2b81cc9@bounce.example.com
         
     | 
| 
      
 31 
     | 
    
         
            +
                #
         
     | 
| 
      
 32 
     | 
    
         
            +
                def self.get_parsed_address(address)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  if match_data = Replyr.route.match(address)
         
     | 
| 
      
 34 
     | 
    
         
            +
                    match_data
         
     | 
| 
      
 35 
     | 
    
         
            +
                  else
         
     | 
| 
      
 36 
     | 
    
         
            +
                    raise ArgumentError, "Malformed reply/bounce email address."
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
                
         
     | 
| 
      
 40 
     | 
    
         
            +
                # Creates a token from the passed model (and user if passed) 
         
     | 
| 
      
 41 
     | 
    
         
            +
                # Uses the configured secret as a salt
         
     | 
| 
      
 42 
     | 
    
         
            +
                #
         
     | 
| 
      
 43 
     | 
    
         
            +
                def create_token(model, user = nil)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  user_id = user.present? ? id_from_model(user) : nil
         
     | 
| 
      
 45 
     | 
    
         
            +
                  model_id = id_from_model(model)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  model_name = normalized_model_name(model)
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  OpenSSL::HMAC.hexdigest(
         
     | 
| 
      
 49 
     | 
    
         
            +
                    OpenSSL::Digest.new('sha1'),
         
     | 
| 
      
 50 
     | 
    
         
            +
                    Replyr.config.secret,
         
     | 
| 
      
 51 
     | 
    
         
            +
                    [user_id, model_name, model_id].compact.join("-")
         
     | 
| 
      
 52 
     | 
    
         
            +
                  )
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
                    
         
     | 
| 
      
 55 
     | 
    
         
            +
                # Check if a given token is valid
         
     | 
| 
      
 56 
     | 
    
         
            +
                #
         
     | 
| 
      
 57 
     | 
    
         
            +
                def token_valid?(token)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  token == self.token
         
     | 
| 
      
 59 
     | 
    
         
            +
                end
         
     | 
| 
      
 60 
     | 
    
         
            +
                
         
     | 
| 
      
 61 
     | 
    
         
            +
                # Ensure a given token is valid
         
     | 
| 
      
 62 
     | 
    
         
            +
                #
         
     | 
| 
      
 63 
     | 
    
         
            +
                def ensure_valid_token!(token)
         
     | 
| 
      
 64 
     | 
    
         
            +
                  raise(RuntimeError, "Token invalid.") unless token_valid?(token)
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
                
         
     | 
| 
      
 67 
     | 
    
         
            +
                
         
     | 
| 
      
 68 
     | 
    
         
            +
              end
         
     | 
| 
      
 69 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,52 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'openssl'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Replyr
         
     | 
| 
      
 4 
     | 
    
         
            +
              class BounceAddress
         
     | 
| 
      
 5 
     | 
    
         
            +
                include AddressBuilder
         
     | 
| 
      
 6 
     | 
    
         
            +
                
         
     | 
| 
      
 7 
     | 
    
         
            +
                attr_accessor :model
         
     | 
| 
      
 8 
     | 
    
         
            +
                
         
     | 
| 
      
 9 
     | 
    
         
            +
                # Create a new reply address from a given user and model
         
     | 
| 
      
 10 
     | 
    
         
            +
                #
         
     | 
| 
      
 11 
     | 
    
         
            +
                def initialize(model)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @model = model
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
                
         
     | 
| 
      
 15 
     | 
    
         
            +
                # Create a reply address from a given address string
         
     | 
| 
      
 16 
     | 
    
         
            +
                # Checks for validity of address and raises an ArgumentError
         
     | 
| 
      
 17 
     | 
    
         
            +
                # if it's invalid.
         
     | 
| 
      
 18 
     | 
    
         
            +
                #
         
     | 
| 
      
 19 
     | 
    
         
            +
                def self.new_from_address(address)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  parts = AddressBuilder.get_parsed_address(address)
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  model_class = AddressBuilder.class_from_normalized_model_name(parts[:model_name])
         
     | 
| 
      
 23 
     | 
    
         
            +
                  model = model_class.find(parts[:model_id])
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  address = new(model)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  address.ensure_valid_token!(parts[:token])
         
     | 
| 
      
 27 
     | 
    
         
            +
                  address
         
     | 
| 
      
 28 
     | 
    
         
            +
                rescue
         
     | 
| 
      
 29 
     | 
    
         
            +
                  Replyr.logger.warn "Bounce email address invalid."
         
     | 
| 
      
 30 
     | 
    
         
            +
                  nil
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
                
         
     | 
| 
      
 33 
     | 
    
         
            +
                # Returs the token from this address
         
     | 
| 
      
 34 
     | 
    
         
            +
                #
         
     | 
| 
      
 35 
     | 
    
         
            +
                def token
         
     | 
| 
      
 36 
     | 
    
         
            +
                  create_token(@model)
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                # Returns the address string
         
     | 
| 
      
 40 
     | 
    
         
            +
                # (e.g bounce-newsletter-12-01ce26dc69094af9246ea7e7ce9970aff2b81cc9@bounce.example.com)
         
     | 
| 
      
 41 
     | 
    
         
            +
                #
         
     | 
| 
      
 42 
     | 
    
         
            +
                def address
         
     | 
| 
      
 43 
     | 
    
         
            +
                  model_id = id_from_model(@model)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  model_name = normalized_model_name(@model)
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  local_part = [Replyr.config.bounce_prefix, model_name, model_id, token].join("-")
         
     | 
| 
      
 47 
     | 
    
         
            +
                  "#{local_part}@#{Replyr.config.bounce_host}"
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
                alias_method :to_s, :address
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
              end
         
     | 
| 
      
 52 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/replyr/config.rb
    CHANGED
    
    | 
         @@ -1,17 +1,30 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Replyr
         
     | 
| 
       2 
2 
     | 
    
         
             
              class Config
         
     | 
| 
       3 
     | 
    
         
            -
                attr_accessor : 
     | 
| 
      
 3 
     | 
    
         
            +
                attr_accessor :reply_prefix, 
         
     | 
| 
      
 4 
     | 
    
         
            +
                              :reply_host, 
         
     | 
| 
      
 5 
     | 
    
         
            +
                              :bounce_prefix,
         
     | 
| 
      
 6 
     | 
    
         
            +
                              :bounce_host,
         
     | 
| 
      
 7 
     | 
    
         
            +
                              :secret, 
         
     | 
| 
      
 8 
     | 
    
         
            +
                              :user_class
         
     | 
| 
       4 
9 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
                def  
     | 
| 
       6 
     | 
    
         
            -
                  @ 
     | 
| 
      
 10 
     | 
    
         
            +
                def reply_prefix
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @reply_prefix || "reply"
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                def bounce_prefix
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @bounce_prefix || "bounce"
         
     | 
| 
       7 
16 
     | 
    
         
             
                end
         
     | 
| 
       8 
17 
     | 
    
         | 
| 
       9 
18 
     | 
    
         
             
                def user_class
         
     | 
| 
       10 
19 
     | 
    
         
             
                  @user_class || User
         
     | 
| 
       11 
20 
     | 
    
         
             
                end
         
     | 
| 
       12 
21 
     | 
    
         | 
| 
       13 
     | 
    
         
            -
                def  
     | 
| 
       14 
     | 
    
         
            -
                  @ 
     | 
| 
      
 22 
     | 
    
         
            +
                def reply_host
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @reply_host || (raise RuntimeError, "Replyr.config.reply_host is nil. Please set a host in an initializer.")
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def bounce_host
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @bounce_host || (raise RuntimeError, "Replyr.config.bounce_host is nil. Please set a host in an initializer.")
         
     | 
| 
       15 
28 
     | 
    
         
             
                end
         
     | 
| 
       16 
29 
     | 
    
         | 
| 
       17 
30 
     | 
    
         
             
                def secret
         
     | 
| 
         @@ -1,8 +1,9 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            module Replyr
         
     | 
| 
       2 
     | 
    
         
            -
              class  
     | 
| 
       3 
     | 
    
         
            -
                attr_accessor :to, :from, :subject, :body, :files
         
     | 
| 
      
 2 
     | 
    
         
            +
              class Email
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_accessor :to, :from, :subject, :body, :files, :mail
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
                def initialize(mail)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  self.mail = mail
         
     | 
| 
       6 
7 
     | 
    
         
             
                  self.to = mail.to.first
         
     | 
| 
       7 
8 
     | 
    
         
             
                  self.from = mail.from.first
         
     | 
| 
       8 
9 
     | 
    
         
             
                  self.subject = mail.subject
         
     | 
| 
         @@ -28,15 +29,30 @@ module Replyr 
     | 
|
| 
       28 
29 
     | 
    
         
             
                # Checks if this incoming mail is a reply email
         
     | 
| 
       29 
30 
     | 
    
         
             
                #
         
     | 
| 
       30 
31 
     | 
    
         
             
                def is_reply_email?
         
     | 
| 
       31 
     | 
    
         
            -
                  to.starts_with?(Replyr.config. 
     | 
| 
      
 32 
     | 
    
         
            +
                  to.starts_with?(Replyr.config.reply_prefix)
         
     | 
| 
       32 
33 
     | 
    
         
             
                end
         
     | 
| 
       33 
34 
     | 
    
         | 
| 
       34 
35 
     | 
    
         
             
                def stripped_body
         
     | 
| 
       35 
36 
     | 
    
         
             
                  EmailReplyParser.parse_reply(body, from).to_s.force_encoding("UTF-8")
         
     | 
| 
       36 
37 
     | 
    
         
             
                end
         
     | 
| 
       37 
38 
     | 
    
         | 
| 
      
 39 
     | 
    
         
            +
                def is_bounce_email?
         
     | 
| 
      
 40 
     | 
    
         
            +
                  mail.bounced? || false
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
                
         
     | 
| 
      
 43 
     | 
    
         
            +
                def failed_permanently?
         
     | 
| 
      
 44 
     | 
    
         
            +
                  !mail.retryable? || false
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
              
         
     | 
| 
       38 
47 
     | 
    
         
             
                def process
         
     | 
| 
       39 
     | 
    
         
            -
                  if  
     | 
| 
      
 48 
     | 
    
         
            +
                  if is_bounce_email?
         
     | 
| 
      
 49 
     | 
    
         
            +
                    if bounce_address = BounceAddress.new_from_address(to)
         
     | 
| 
      
 50 
     | 
    
         
            +
                      bounce_address.model.handle_bounce(mail.final_recipient)
         
     | 
| 
      
 51 
     | 
    
         
            +
                      true
         
     | 
| 
      
 52 
     | 
    
         
            +
                    else
         
     | 
| 
      
 53 
     | 
    
         
            +
                      false
         
     | 
| 
      
 54 
     | 
    
         
            +
                    end
         
     | 
| 
      
 55 
     | 
    
         
            +
                  elsif is_reply_email?
         
     | 
| 
       40 
56 
     | 
    
         
             
                    if reply_address = ReplyAddress.new_from_address(to)
         
     | 
| 
       41 
57 
     | 
    
         
             
                      reply_address.model.handle_reply(reply_address.user, stripped_body, files)
         
     | 
| 
       42 
58 
     | 
    
         
             
                      true
         
     | 
    
        data/lib/replyr/engine.rb
    CHANGED
    
    
| 
         @@ -0,0 +1,35 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Replyr
         
     | 
| 
      
 2 
     | 
    
         
            +
              module HandleBounce
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                def self.included(base)
         
     | 
| 
      
 5 
     | 
    
         
            +
                  base.extend ClassMethods  
         
     | 
| 
      
 6 
     | 
    
         
            +
                end
         
     | 
| 
      
 7 
     | 
    
         
            +
              
         
     | 
| 
      
 8 
     | 
    
         
            +
                module ClassMethods 
         
     | 
| 
      
 9 
     | 
    
         
            +
                
         
     | 
| 
      
 10 
     | 
    
         
            +
                  # Usage:
         
     | 
| 
      
 11 
     | 
    
         
            +
                  # class Comment < ActiveRecord::Base
         
     | 
| 
      
 12 
     | 
    
         
            +
                  #   handle_bounce do |comment, email|
         
     | 
| 
      
 13 
     | 
    
         
            +
                  #     # your custom code (e.g. mark email as invalid)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  #   end
         
     | 
| 
      
 15 
     | 
    
         
            +
                  # end
         
     | 
| 
      
 16 
     | 
    
         
            +
                  #
         
     | 
| 
      
 17 
     | 
    
         
            +
                  def handle_bounce(*options, &block)	
         
     | 
| 
      
 18 
     | 
    
         
            +
                		options = options.extract_options!
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                    define_method :handle_bounce do |email|
         
     | 
| 
      
 21 
     | 
    
         
            +
                      block.call(self, email) 
         
     | 
| 
      
 22 
     | 
    
         
            +
                    end
         
     | 
| 
      
 23 
     | 
    
         
            +
              
         
     | 
| 
      
 24 
     | 
    
         
            +
                    define_method :bounce_address_object do
         
     | 
| 
      
 25 
     | 
    
         
            +
                      BounceAddress.new(self)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    end
         
     | 
| 
      
 27 
     | 
    
         
            +
                    
         
     | 
| 
      
 28 
     | 
    
         
            +
                    define_method :bounce_address do
         
     | 
| 
      
 29 
     | 
    
         
            +
                      bounce_address_object.to_s
         
     | 
| 
      
 30 
     | 
    
         
            +
                    end
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
            end
         
     |