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 +4 -4
- data/README.md +21 -15
- data/app/controllers/emailbutler/webhooks_controller.rb +57 -4
- data/config/routes.rb +2 -1
- data/lib/emailbutler/configuration.rb +52 -3
- data/lib/emailbutler/container.rb +29 -0
- data/lib/emailbutler/version.rb +1 -1
- data/lib/emailbutler/webhooks/mappers/resend.rb +35 -0
- data/lib/emailbutler/webhooks/mappers/sendgrid.rb +0 -4
- data/lib/emailbutler/webhooks/mappers/smtp2go.rb +0 -4
- data/lib/emailbutler/webhooks/receiver.rb +1 -17
- data/lib/emailbutler.rb +21 -10
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e06b545b530403687a7844d31538ea50de73cffbc8f225ab5a849fe13d7678b0
|
4
|
+
data.tar.gz: c6fdc2b0fcf1afb430c0b1c80b2b1b89f9273007a54f97b3c96ddbad44a1bef2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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.
|
38
|
-
config.
|
39
|
-
config.
|
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
|
-
|
63
|
-
|
64
|
-
payload: receiver_params
|
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
|
-
|
9
|
-
|
10
|
-
payload:
|
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
|
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#
|
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
|
-
|
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
|
data/lib/emailbutler/version.rb
CHANGED
@@ -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
|
@@ -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
|
-
|
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
|
-
|
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:
|
38
|
-
# Returns Emailbutler::DSL instance.
|
46
|
+
# Public: Returns Emailbutler::DSL instance.
|
39
47
|
def instance
|
40
|
-
|
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.
|
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
|
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
|