emailbutler 0.7.6 → 0.8.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7a1a7e4cc9dad6d9958abcb9e832e85d6c74a2c44a71c63bc42e7e3be59debc
4
- data.tar.gz: 490337b5946df84317357c9f22d9d189d0434ff215726533b3dc934479b99eb3
3
+ metadata.gz: e06b545b530403687a7844d31538ea50de73cffbc8f225ab5a849fe13d7678b0
4
+ data.tar.gz: c6fdc2b0fcf1afb430c0b1c80b2b1b89f9273007a54f97b3c96ddbad44a1bef2
5
5
  SHA512:
6
- metadata.gz: 53580acc1672780cc1b5eca024fc68868cf8ab3f3d2cadc6a02f4edb70787ba1c87b583a04703a1bca718ecc9ef80254d6f6a5d288824cb7a05247ef38c30a50
7
- data.tar.gz: 7565afd9b680a42d2dee0b500b39773e696b2e80a53d7eb7e1f8ddb3f686dca7ef65af0548471416d18cbe2cb4bfd68e6b07128007439341a6e611a29d2e73a8
6
+ metadata.gz: 71919a2533d61eb98ea21ef74266c261f1569acaf8ae2193fa30eef083b2ae06c659ec38af299d86d67d5b09f06ed9391b7fd63ba143687bd2592d071bbac916
7
+ data.tar.gz: ea46055aa238b71c0c237138caf69e2ed8b4580d0956e95274e3762071b7ae72e273ddf6fc43147634d0723d8b5966b31b8a25d89b8ed71901937fa7c1f9e12a
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Emailbutler
2
2
  Simple email tracker for Ruby on Rails applications.
3
- Emailbutler allows you to track delivery status of emails sent by your app through Sendgrid and/or SMTP2GO.
3
+ Emailbutler allows you to track delivery status of emails sent by your app through Sendgrid, SMTP2GO, Resend.
4
4
 
5
5
  There are situations when you need to check whether a certain letter or certain type of letters was successfully sent from the application, and through the UI of some providers you can try to find such a letter by the recipient or the subject of the letter, but sometimes it's not enough.
6
6
 
@@ -27,16 +27,17 @@ $ rails db:migrate
27
27
 
28
28
  Add configuration line to config/initializers/emailbutler.rb:
29
29
 
30
- #### ActiveRecord
30
+ #### For ActiveRecord
31
31
 
32
32
  ```ruby
33
33
  require 'emailbutler/adapters/active_record'
34
34
 
35
35
  Emailbutler.configure do |config|
36
- config.adapter = Emailbutler::Adapters::ActiveRecord.new
37
- config.ui_username = 'username'
38
- config.ui_password = 'password'
39
- config.ui_secured_environments = ['production']
36
+ config.adapter = Emailbutler::Adapters::ActiveRecord.new # required
37
+ config.providers = %w[sendgrid smtp2go resend] # optional
38
+ config.ui_username = 'username' # optional
39
+ config.ui_password = 'password' # optional
40
+ config.ui_secured_environments = ['production'] # optional
40
41
  end
41
42
  ```
42
43
 
@@ -59,9 +60,9 @@ class SendgridController < ApplicationController
59
60
  def create
60
61
  ... you can add some logic here
61
62
 
62
- ::Emailbutler::Webhooks::Receiver.call(
63
- user_agent: request.headers['HTTP_USER_AGENT'],
64
- payload: receiver_params.to_h
63
+ Emailbutler::Container.resolve(:webhooks_receiver).call(
64
+ mapper: Emailbutler::Container.resolve(:sendgrid_mapper),
65
+ payload: receiver_params
65
66
  )
66
67
 
67
68
  head :ok
@@ -70,10 +71,7 @@ class SendgridController < ApplicationController
70
71
  private
71
72
 
72
73
  def receiver_params
73
- params.permit(
74
- 'event', 'sendtime', 'message-id',
75
- '_json' => %w[event timestamp smtp-id sg_message_id]
76
- )
74
+ params.permit('event', 'sendtime', 'message-id').to_h
77
75
  end
78
76
  end
79
77
  ```
@@ -106,7 +104,7 @@ end
106
104
 
107
105
  - go to [Mail settings](https://app.sendgrid.com/settings/mail_settings),
108
106
  - turn on Event Webhook,
109
- - in the HTTP POST URL field, paste the URL to webhook controller of your app,
107
+ - in the HTTP POST URL field, paste the URL to webhook controller of your app (host/emailbutler/webhooks/sendgrid),
110
108
  - select all deliverability data,
111
109
  - save settings.
112
110
 
@@ -114,7 +112,15 @@ end
114
112
 
115
113
  - go to [Mail settings](https://app-eu.smtp2go.com/settings/webhooks),
116
114
  - turn on Webhooks,
117
- - in the HTTP POST URL field, paste the URL to webhook controller of your app,
115
+ - in the HTTP POST URL field, paste the URL to webhook controller of your app (host/emailbutler/webhooks/smtp2go),
116
+ - select all deliverability data,
117
+ - save settings.
118
+
119
+ #### Resend
120
+
121
+ - go to [Mail settings](https://resend.com/webhooks),
122
+ - add Webhook,
123
+ - in the Endpoint URL field, paste the URL to webhook controller of your app (host/emailbutler/webhooks/resend),
118
124
  - select all deliverability data,
119
125
  - save settings.
120
126
 
@@ -2,12 +2,26 @@
2
2
 
3
3
  module Emailbutler
4
4
  class WebhooksController < Emailbutler::ApplicationController
5
+ # deprecated constants
6
+ SENDGRID_USER_AGENT = 'SendGrid Event API'
7
+ SMTP2GO_USER_AGENT = 'Go-http-client/1.1'
8
+
5
9
  skip_before_action :verify_authenticity_token
10
+ before_action :validate_provider, only: %i[create]
11
+
12
+ def create_deprecated
13
+ Emailbutler::Container.resolve(:webhooks_receiver).call(
14
+ mapper: receiver_mapper_deprecated(request.headers['HTTP_USER_AGENT']),
15
+ payload: mapper_params_deprecated.to_h
16
+ )
17
+
18
+ head :ok
19
+ end
6
20
 
7
21
  def create
8
- ::Emailbutler::Webhooks::Receiver.call(
9
- user_agent: request.headers['HTTP_USER_AGENT'],
10
- payload: receiver_params.to_h
22
+ Emailbutler::Container.resolve(:webhooks_receiver).call(
23
+ mapper: receiver_mapper,
24
+ payload: mapper_params.to_h
11
25
  )
12
26
 
13
27
  head :ok
@@ -15,7 +29,46 @@ module Emailbutler
15
29
 
16
30
  private
17
31
 
18
- def receiver_params
32
+ def validate_provider
33
+ head :ok if Emailbutler.configuration.providers.exclude?(params[:provider])
34
+ end
35
+
36
+ def receiver_mapper
37
+ case params[:provider]
38
+ when 'sendgrid' then Emailbutler::Container.resolve(:sendgrid_mapper)
39
+ when 'smtp2go' then Emailbutler::Container.resolve(:smtp2go_mapper)
40
+ when 'resend' then Emailbutler::Container.resolve(:resend_mapper)
41
+ end
42
+ end
43
+
44
+ def mapper_params
45
+ case params[:provider]
46
+ when 'sendgrid' then sendgrid_params
47
+ when 'smtp2go' then smtp2go_params
48
+ when 'resend' then resend_params
49
+ end
50
+ end
51
+
52
+ def sendgrid_params
53
+ params.permit('_json' => %w[smtp-id event timestamp sg_message_id])
54
+ end
55
+
56
+ def smtp2go_params
57
+ params.permit('event', 'sendtime', 'message-id')
58
+ end
59
+
60
+ def resend_params
61
+ params.permit('type', 'created_at', 'data' => %w[email_id])
62
+ end
63
+
64
+ def receiver_mapper_deprecated(user_agent)
65
+ case user_agent
66
+ when SENDGRID_USER_AGENT then Emailbutler::Container.resolve(:sendgrid_mapper)
67
+ when SMTP2GO_USER_AGENT then Emailbutler::Container.resolve(:smtp2go_mapper)
68
+ end
69
+ end
70
+
71
+ def mapper_params_deprecated
19
72
  params.permit(
20
73
  'event', 'sendtime', 'message-id',
21
74
  '_json' => %w[event timestamp smtp-id sg_message_id]
data/config/routes.rb CHANGED
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  Emailbutler::Engine.routes.draw do
4
- post '/webhooks', to: 'webhooks#create'
4
+ post '/webhooks', to: 'webhooks#create_deprecated'
5
+ post '/webhooks/:provider', to: 'webhooks#create'
5
6
 
6
7
  resources :ui, only: %i[index show]
7
8
  namespace :ui do
@@ -2,16 +2,65 @@
2
2
 
3
3
  module Emailbutler
4
4
  class Configuration
5
- attr_accessor :adapter, :ui_username, :ui_password, :ui_secured_environments
5
+ InitializeError = Class.new(StandardError)
6
+
7
+ AVAILABLE_ADAPTERS = %w[
8
+ Emailbutler::Adapters::ActiveRecord
9
+ ].freeze
10
+ AVAILABLE_PROVIDERS = %w[sendgrid smtp2go resend].freeze
11
+
12
+ attr_accessor :adapter, :providers, :ui_username, :ui_password, :ui_secured_environments
6
13
 
7
14
  def initialize
8
15
  @adapter = nil
16
+ @providers = []
9
17
 
10
18
  # It's required to specify these 3 variables to enable basic auth to UI
11
- @ui_username = ''
12
- @ui_password = ''
19
+ @ui_username = nil
20
+ @ui_password = nil
13
21
  # Secured environments variable must directly contains environment names
14
22
  @ui_secured_environments = []
15
23
  end
24
+
25
+ def validate
26
+ validate_adapter
27
+ validate_providers
28
+ validate_username
29
+ validate_password
30
+ validate_secured_environments
31
+ end
32
+
33
+ private
34
+
35
+ def validate_adapter
36
+ raise InitializeError, 'Adapter must be present' if adapter.nil?
37
+ raise InitializeError, 'Invalid adapter' if AVAILABLE_ADAPTERS.exclude?(adapter.class.name)
38
+ end
39
+
40
+ def validate_providers
41
+ raise InitializeError, 'Providers list must be array' unless providers.is_a?(Array)
42
+
43
+ return unless providers.any? { |provider| AVAILABLE_PROVIDERS.exclude?(provider) }
44
+
45
+ raise InitializeError, 'Providers list contain invalid element'
46
+ end
47
+
48
+ def validate_username
49
+ return if ui_username.blank?
50
+ return if ui_username.is_a?(String)
51
+
52
+ raise InitializeError, 'Username must be nil or filled string'
53
+ end
54
+
55
+ def validate_password
56
+ return if ui_password.blank?
57
+ return if ui_password.is_a?(String)
58
+
59
+ raise InitializeError, 'Password must be nil or filled string'
60
+ end
61
+
62
+ def validate_secured_environments
63
+ raise InitializeError, 'Environments list must be array' unless ui_secured_environments.is_a?(Array)
64
+ end
16
65
  end
17
66
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry/container'
4
+ require 'emailbutler/webhooks/mappers/sendgrid'
5
+ require 'emailbutler/webhooks/mappers/smtp2go'
6
+ require 'emailbutler/webhooks/mappers/resend'
7
+ require 'emailbutler/webhooks/receiver'
8
+
9
+ module Emailbutler
10
+ class Container
11
+ extend Dry::Container::Mixin
12
+
13
+ DEFAULT_OPTIONS = { memoize: true }.freeze
14
+
15
+ class << self
16
+ def register(key)
17
+ super(key, DEFAULT_OPTIONS)
18
+ end
19
+ end
20
+
21
+ # webhook mappers
22
+ register(:sendgrid_mapper) { Emailbutler::Webhooks::Mappers::Sendgrid.new }
23
+ register(:smtp2go_mapper) { Emailbutler::Webhooks::Mappers::Smtp2Go.new }
24
+ register(:resend_mapper) { Emailbutler::Webhooks::Mappers::Resend.new }
25
+
26
+ # webhook receiver
27
+ register(:webhooks_receiver) { Emailbutler::Webhooks::Receiver.new }
28
+ end
29
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Emailbutler
4
- VERSION = '0.7.6'
4
+ VERSION = '0.8.1'
5
5
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Emailbutler
4
+ module Webhooks
5
+ module Mappers
6
+ class Resend
7
+ DELIVERABILITY_MAPPER = {
8
+ 'email.sent' => 'processed',
9
+ 'email.delivered' => 'delivered',
10
+ 'email.opened' => 'delivered',
11
+ 'email.clicked' => 'delivered',
12
+ 'email.delivered_delayed' => 'failed',
13
+ 'email.bounced' => 'failed',
14
+ 'email.complained' => 'failed'
15
+ }.freeze
16
+
17
+ def call(payload:)
18
+ payload.stringify_keys!
19
+ # message-id contains data like <uuid>
20
+ message_uuid = payload.dig('data', 'email_id')
21
+ status = DELIVERABILITY_MAPPER[payload['type']]
22
+ return [] if message_uuid.nil? || status.nil?
23
+
24
+ [
25
+ {
26
+ message_uuid: message_uuid,
27
+ status: status,
28
+ timestamp: payload['created_at'] ? DateTime.parse(payload['created_at']).utc : nil
29
+ }
30
+ ]
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -15,10 +15,6 @@ module Emailbutler
15
15
  'spamreport' => 'failed'
16
16
  }.freeze
17
17
 
18
- def self.call(...)
19
- new.call(...)
20
- end
21
-
22
18
  def call(payload:)
23
19
  payload['_json'].filter_map { |message|
24
20
  message.stringify_keys!
@@ -14,10 +14,6 @@ module Emailbutler
14
14
  'spam' => 'failed'
15
15
  }.freeze
16
16
 
17
- def self.call(...)
18
- new.call(...)
19
- end
20
-
21
17
  def call(payload:)
22
18
  payload.stringify_keys!
23
19
  # message-id contains data like <uuid>
@@ -1,25 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'emailbutler/webhooks/mappers/sendgrid'
4
- require 'emailbutler/webhooks/mappers/smtp2go'
5
-
6
3
  module Emailbutler
7
4
  module Webhooks
8
5
  class Receiver
9
- SENDGRID_USER_AGENT = 'SendGrid Event API'
10
- SMTP2GO_USER_AGENT = 'Go-http-client/1.1'
11
-
12
- RECEIVERS_MAPPER = {
13
- 'SendGrid Event API' => Emailbutler::Webhooks::Mappers::Sendgrid,
14
- 'Go-http-client/1.1' => Emailbutler::Webhooks::Mappers::Smtp2Go
15
- }.freeze
16
-
17
- def self.call(...)
18
- new.call(...)
19
- end
20
-
21
- def call(user_agent:, payload:)
22
- mapper = RECEIVERS_MAPPER[user_agent]
6
+ def call(mapper:, payload:)
23
7
  return unless mapper
24
8
 
25
9
  mapper
data/lib/emailbutler.rb CHANGED
@@ -6,38 +6,49 @@ require 'emailbutler/version'
6
6
  require 'emailbutler/engine'
7
7
  require 'emailbutler/configuration'
8
8
  require 'emailbutler/dsl'
9
- require 'emailbutler/webhooks/receiver'
10
9
  require 'emailbutler/helpers'
11
10
  require 'emailbutler/mailers/helpers'
11
+ require 'emailbutler/container'
12
12
 
13
13
  module Emailbutler
14
14
  extend self
15
15
  extend Forwardable
16
16
 
17
- # Public: Given an adapter returns a handy DSL to all the emailbutler goodness.
18
- def new(adapter)
19
- DSL.new(adapter)
20
- end
21
-
22
17
  # Public: Configure emailbutler.
23
18
  #
19
+ # require 'emailbutler/adapters/active_record'
20
+ #
24
21
  # Emailbutler.configure do |config|
25
22
  # config.adapter = Emailbutler::Adapters::ActiveRecord.new
23
+ # config.providers = %i[sendgrid]
26
24
  # end
27
25
  #
28
26
  def configure
27
+ ActiveSupport::Deprecation.new.warn(
28
+ 'Webhook endpoint should receive provider name in params. ' \
29
+ 'Please update webhook url in settings of your SMTP provider.' \
30
+ 'Webhook without provider in params will be removed soon.'
31
+ )
32
+
29
33
  yield configuration
34
+
35
+ configuration.validate
30
36
  end
31
37
 
32
38
  # Public: Returns Emailbutler::Configuration instance.
33
39
  def configuration
34
- @configuration ||= Configuration.new
40
+ return Emailbutler::Container.resolve(:configuration) if Emailbutler::Container.key?(:configuration)
41
+
42
+ Emailbutler::Container.register(:configuration) { Configuration.new }
43
+ Emailbutler::Container.resolve(:configuration)
35
44
  end
36
45
 
37
- # Public: Default per thread emailbutler instance if configured.
38
- # Returns Emailbutler::DSL instance.
46
+ # Public: Returns Emailbutler::DSL instance.
39
47
  def instance
40
- Thread.current[:emailbutler_instance] ||= new(configuration.adapter)
48
+ return Emailbutler::Container.resolve(:instance) if Emailbutler::Container.key?(:instance)
49
+
50
+ Emailbutler::Container.register(:instance) { DSL.new(configuration.adapter) }
51
+ Emailbutler::Container.resolve(:instance)
41
52
  end
42
53
 
43
54
  # Public: All the methods delegated to instance. These should match the interface of Emailbutler::DSL.
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: emailbutler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.6
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bogdanov Anton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-19 00:00:00.000000000 Z
11
+ date: 2024-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-container
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.11.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.11.0
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: pagy
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -196,11 +210,13 @@ files:
196
210
  - lib/emailbutler.rb
197
211
  - lib/emailbutler/adapters/active_record.rb
198
212
  - lib/emailbutler/configuration.rb
213
+ - lib/emailbutler/container.rb
199
214
  - lib/emailbutler/dsl.rb
200
215
  - lib/emailbutler/engine.rb
201
216
  - lib/emailbutler/helpers.rb
202
217
  - lib/emailbutler/mailers/helpers.rb
203
218
  - lib/emailbutler/version.rb
219
+ - lib/emailbutler/webhooks/mappers/resend.rb
204
220
  - lib/emailbutler/webhooks/mappers/sendgrid.rb
205
221
  - lib/emailbutler/webhooks/mappers/smtp2go.rb
206
222
  - lib/emailbutler/webhooks/receiver.rb