sendgrid_actionmailer_adapter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4fc00b2f351543df7b2292bceca96e549c3a03e0
4
+ data.tar.gz: a9ae4a0cb08a0442c7fbe92ded4c108f44f53cd1
5
+ SHA512:
6
+ metadata.gz: b63e0b130b073189f16856705932d12116ca4d59c80c280d127f8a27ebad53826269ed9bbc9fd5bdcca7c20454739805464b6bd4420c3d0d9cf05dabe7fee278
7
+ data.tar.gz: 1ef8d805710cd759e8df2437a5e2ba5e336141a79c47fb69f2bd0b06d51fda99bdbf1cfdc7bc161afcbfca23f9795d4125a562a27fd4ac6881172d3e3a0e7c4a
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 ryu39
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sendgrid_actionmailer_adapter/version'
4
+ require 'sendgrid_actionmailer_adapter/configuration'
5
+ require 'sendgrid_actionmailer_adapter/delivery_method'
6
+
7
+ module SendGridActionMailerAdapter
8
+ def self.configure(&block)
9
+ ::SendGridActionMailerAdapter::Configuration.configure(&block)
10
+ end
11
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SendGridActionMailerAdapter
4
+ class Configuration
5
+ DEFAULT_RETRY_MAX_COUNT = 0
6
+ DEFAULT_RETRY_WAIT_SECONDS = 1.0
7
+
8
+ class << self
9
+ attr_accessor :api_key, :host, :request_headers, :version, :retry_max_count,
10
+ :retry_wait_seconds
11
+
12
+ # Set your configuration with block.
13
+ def configure
14
+ yield(self)
15
+ end
16
+
17
+ # Returns configuration hash.
18
+ #
19
+ # @return [Hash]
20
+ def settings
21
+ @settings ||= {
22
+ sendgrid: {
23
+ api_key: api_key || '',
24
+ host: host,
25
+ request_headers: request_headers,
26
+ version: version
27
+ },
28
+ retry: {
29
+ max_count: retry_max_count || DEFAULT_RETRY_MAX_COUNT,
30
+ wait_seconds: retry_wait_seconds || DEFAULT_RETRY_WAIT_SECONDS
31
+ }
32
+ }.freeze
33
+ end
34
+
35
+ # Reset settings for test.
36
+ def reset!
37
+ self.api_key = nil
38
+ self.host = nil
39
+ self.request_headers = nil
40
+ self.version = nil
41
+ self.retry_max_count = nil
42
+ self.retry_wait_seconds = nil
43
+ @settings = nil
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'base64'
4
+ require 'mail'
5
+ require 'sendgrid-ruby'
6
+ require_relative 'errors'
7
+
8
+ module SendGridActionMailerAdapter
9
+ class Converter
10
+ VALIDATORS = [
11
+ ->(mail) { "'from' is required." if mail.from_addrs.empty? },
12
+ ->(mail) { "'to_addrs' must not be empty." if mail.to_addrs.empty? },
13
+ ->(mail) { "'subject' is required." if mail.subject.nil? || mail.subject.empty? }
14
+ ].freeze
15
+
16
+ CONVERTERS = {
17
+ from: ->(mail) {
18
+ addr = mail[:from].addrs.first
19
+ ::SendGrid::Email.new(email: addr.address, name: addr.display_name)
20
+ },
21
+ subject: ->(mail) { mail.subject },
22
+ personalizations: ->(mail) {
23
+ # Separate emails per each To address.
24
+ # Cc and Bcc addresses are shared with each emails.
25
+ mail[:to].addrs.map do |to_addr|
26
+ ::SendGrid::Personalization.new.tap do |p|
27
+ p.to = ::SendGrid::Email.new(email: to_addr.address, name: to_addr.display_name)
28
+ Array(mail[:cc]&.addrs).each do |addr|
29
+ p.cc = ::SendGrid::Email.new(email: addr.address, name: addr.display_name)
30
+ end
31
+ Array(mail[:bcc]&.addrs).each do |addr|
32
+ p.bcc = ::SendGrid::Email.new(email: addr.address, name: addr.display_name)
33
+ end
34
+ end
35
+ end
36
+ },
37
+ contents: ->(mail) {
38
+ ::SendGrid::Content.new(type: mail.mime_type, value: mail.body.to_s)
39
+ },
40
+ attachments: ->(mail) {
41
+ mail.attachments.map do |attachment|
42
+ ::SendGrid::Attachment.new.tap do |sendgrid_attachment|
43
+ sendgrid_attachment.type = attachment.mime_type
44
+ sendgrid_attachment.content = ::Base64.encode64(attachment.body.raw_source)
45
+ sendgrid_attachment.filename = ::Mail::Encodings.decode_encode(
46
+ attachment.content_type_parameters['filename'], :decode
47
+ )
48
+ sendgrid_attachment.content_id = attachment.cid
49
+ end
50
+ end
51
+ },
52
+ categories: ->(mail) {
53
+ return nil if mail['categories'].nil?
54
+ # FIXME: Separator ', ' is dependant on Mail::UnstructuredField implementation,
55
+ # this may occur unexpected behaviour on 'mail' gem update.
56
+ mail['categories'].value.split(', ').map { |c| ::SendGrid::Category.new(name: c) }
57
+ },
58
+ send_at: ->(mail) { mail['send_at'].value.to_i if mail['send_at'] },
59
+ reply_to: ->(mail) {
60
+ addr = mail[:reply_to]&.addrs&.first
61
+ ::SendGrid::Email.new(email: addr.address, name: addr.display_name) if addr
62
+ }
63
+ }.freeze
64
+
65
+ class << self
66
+ # Convert Mail::Message to SendGrid::Mail.
67
+ #
68
+ # @param [Message::Mail] mail
69
+ # @return [SendGrid::Mail]
70
+ # @raise [SendGridActionMailerAdapter::ValidationError]
71
+ def to_sendgrid_mail(mail)
72
+ validate!(mail)
73
+ convert(mail)
74
+ end
75
+
76
+ private
77
+
78
+ def validate!(mail)
79
+ error_messages = VALIDATORS.map { |validator| validator.call(mail) }.compact
80
+ unless error_messages.empty?
81
+ raise SendGridActionMailerAdapter::ValidationError, "Validation error, #{error_messages}"
82
+ end
83
+ end
84
+
85
+ def convert(mail)
86
+ sendgrid_mail = ::SendGrid::Mail.new
87
+ CONVERTERS.each do |attr_name, converter|
88
+ Array(converter.call(mail)).each do |attr_val|
89
+ sendgrid_mail.send("#{attr_name}=", attr_val)
90
+ end
91
+ end
92
+ sendgrid_mail
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sendgrid/client'
4
+ require_relative 'configuration'
5
+ require_relative 'converter'
6
+ require_relative 'errors'
7
+
8
+ module SendGridActionMailerAdapter
9
+ class DeliveryMethod
10
+ attr_reader :settings
11
+
12
+ # Initialize this instance.
13
+ #
14
+ # This method is expected to be called from Mail::Message class.
15
+ #
16
+ # @params [Hash] settings settings parameters which you want to override.
17
+ # @options settings [String] :api_key Your SendGrid Web API key.
18
+ # @options settings [String] :host Base host FQDN of API endpoint.
19
+ # @options settings [Hash] :request_headers Request headers which you want to override.
20
+ # @options settings [String] :version API version string, eg: 'v3'.
21
+ # @options settings [Integer] :retry_max_count Count for retry, Default is 0(Do not retry).
22
+ # @options settings [Integer, Float] :retry_wait_sec Wait seconds for next retry, Default is 1.
23
+ def initialize(settings)
24
+ @settings = ::SendGridActionMailerAdapter::Configuration.settings.merge(settings)
25
+ end
26
+
27
+ # Deliver a mail via SendGrid Web API.
28
+ #
29
+ # This method is called from Mail::Message#deliver!.
30
+ #
31
+ # @param [Mail::Message] mail Mail::Message object which you want to send.
32
+ # @raise [SendGridActionMailerAdapter::ValidationError] when validation error occurred.
33
+ # @raise [SendGridActionMailerAdapter::ApiError] when SendGrid Web API returns error response.
34
+ def deliver!(mail)
35
+ sendgrid_mail = ::SendGridActionMailerAdapter::Converter.to_sendgrid_mail(mail)
36
+ body = sendgrid_mail.to_json
37
+ client = ::SendGrid::API.new(@settings[:sendgrid]).client.mail._('send')
38
+
39
+ with_retry(@settings[:retry]) do
40
+ response = client.post(request_body: body)
41
+ handle_response!(response)
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def with_retry(max_count:, wait_seconds:)
48
+ tryable_count = max_count + 1
49
+ begin
50
+ tryable_count -= 1
51
+ yield
52
+ rescue ::SendGridActionMailerAdapter::ApiClientError => _e
53
+ raise
54
+ rescue => _e
55
+ if tryable_count > 0
56
+ sleep(wait_seconds)
57
+ retry
58
+ end
59
+ raise
60
+ end
61
+ end
62
+
63
+ # @see https://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html
64
+ def handle_response!(response)
65
+ case response.status_code.to_i
66
+ when (200..299)
67
+ response
68
+ when (400..499)
69
+ raise ::SendGridActionMailerAdapter::ApiClientError, response
70
+ else
71
+ raise ::SendGridActionMailerAdapter::ApiUnexpectedError, response
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SendGridActionMailerAdapter
4
+ class ValidationError < StandardError; end
5
+
6
+ class ApiError < StandardError
7
+ attr_accessor :response
8
+
9
+ def initialize(response)
10
+ super("SendGrid API returns error, #{response.inspect}")
11
+ @response = response
12
+ end
13
+ end
14
+ class ApiClientError < ApiError; end
15
+ class ApiUnexpectedError < ApiError; end
16
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SendGridActionMailerAdapter
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sendgrid_actionmailer_adapter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - ryu39
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-05-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sendgrid-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: webmock
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.48.1
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.48.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: mail
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: A ActionMailer adapter using SendGrid Web API v3.
112
+ email:
113
+ - dev.ryu39@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - LICENSE.txt
119
+ - lib/sendgrid_actionmailer_adapter.rb
120
+ - lib/sendgrid_actionmailer_adapter/configuration.rb
121
+ - lib/sendgrid_actionmailer_adapter/converter.rb
122
+ - lib/sendgrid_actionmailer_adapter/delivery_method.rb
123
+ - lib/sendgrid_actionmailer_adapter/errors.rb
124
+ - lib/sendgrid_actionmailer_adapter/version.rb
125
+ homepage: https://github.com/ryu39/sendgrid_actionmailer_adapter
126
+ licenses:
127
+ - MIT
128
+ metadata: {}
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '2.3'
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements: []
144
+ rubyforge_project:
145
+ rubygems_version: 2.6.11
146
+ signing_key:
147
+ specification_version: 4
148
+ summary: A ActionMailer adapter using SendGrid Web API v3
149
+ test_files: []