tt-mail-ses 1.1.0.pre

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5199d52725461a602ecd0ef5514c670248f1cd212ccdaac40b337b5b53da7660
4
+ data.tar.gz: 550bcb9cdd21e6a545358119726de54d6dba80613f2be39f35e284844b5484ec
5
+ SHA512:
6
+ metadata.gz: 73669644af86b647f248532e6988790a82cc2620aeebb9689f764ed5be45dab391751c038ecf7323cad734e467b0d7be0288b0b9a756f8f4cdb4a7f0ea367c93
7
+ data.tar.gz: b8cb0539bbc43413e76c3f1fe9e357775561d1ac51c5cce556f241d73101b6e54330a526e96da1d81d43dbdd131a7463066057fa8e66c56d5e4790a221db07d1
data/CHANGELOG.md ADDED
@@ -0,0 +1,50 @@
1
+ # Changelog
2
+
3
+ ### 1.2.0.pre
4
+ - Support options via message headers [#2](https://github.com/Teamtailor/mail-ses/pull/2)
5
+
6
+ ### 1.1.0
7
+
8
+ - Mail Gem: Bump minimum version dependency to 2.8.1.
9
+
10
+ ### 1.0.5
11
+
12
+ - Pass-thru invalid email addresses.
13
+
14
+ ### 1.0.4
15
+
16
+ - Fix missing method error related to message headers.
17
+
18
+ ### 1.0.3
19
+
20
+ - Support UTF-8 chars in from, to, etc addresses.
21
+
22
+ ### 1.0.2
23
+
24
+ - Fix labels in being stripped from email addresses.
25
+ - Support Reply-To address.
26
+
27
+ ### 1.0.1
28
+
29
+ - Add compatibility with Mail gem 2.8.0.
30
+
31
+ ### 1.0.0
32
+
33
+ - BREAKING CHANGE: Upgrade to AWS Ruby SDK v3 - SESv2 API ([@khrvi](https://github.com/khrvi))
34
+ - Drop support for Ruby 2.5 and earlier.
35
+ - Switch CI from Travis to Github Actions.
36
+ - Add Rubocop to CI.
37
+ - Refactor code.
38
+
39
+ ### 0.1.2
40
+
41
+ - Fix: Add #settings method for conformity with other Mail delivery methods.
42
+
43
+ ### 0.1.1
44
+
45
+ - Fix: Remove Base64 encoding from message body.
46
+
47
+ ### 0.1.0
48
+
49
+ - Initial release of gem.
50
+ - Support for sending ActionMailer mails via AWS SDK v3.
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ Additional modifications copyright (c) 2024 Teamtailor AB
2
+
3
+ Copyright (c) 2018 TableCheck Inc.
4
+
5
+ AWS::SES originally copyright (c) 2011 Drew V. Blas <drew.blas@gmail.com>
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining
8
+ a copy of this software and associated documentation files (the
9
+ "Software"), to deal in the Software without restriction, including
10
+ without limitation the rights to use, copy, modify, merge, publish,
11
+ distribute, sublicense, and/or sell copies of the Software, and to
12
+ permit persons to whom the Software is furnished to do so, subject to
13
+ the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,143 @@
1
+ [![Gem Version](https://badge.fury.io/rb/mail-ses.svg)](http://badge.fury.io/rb/mail-ses)
2
+ [![Github Actions](https://github.com/tablecheck/mail-ses/actions/workflows/test.yml/badge.svg)](https://github.com/tablecheck/mail-ses/actions/workflows/test.yml)
3
+
4
+ # Mail::SES
5
+
6
+ Mail::SES is a mail delivery method handler for Amazon SES (Simple Email Service) which can be used with Rails' [Action Mailer](https://guides.rubyonrails.org/action_mailer_basics.html).
7
+
8
+ This gem is inspired by [Drew Blas' AWS::SES gem](https://github.com/drewblas/aws-ses),
9
+ but uses the official [AWS SDK for Ruby v3 - SESv2](https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/SESV2.html) under-the-hood.
10
+ By passing parameters through to the SDK, this gem supports greater flexibility with less code (including IAM instance profiles, retry parameters, etc.)
11
+
12
+ ### Compatibility
13
+
14
+ * Ruby 2.6+
15
+ * Ruby on Rails 3.2+
16
+ * Mail gem 2.8.1+
17
+ * AWS SDK for Ruby v3 - SESv2
18
+
19
+ Please use version 0.1.x of this gem for legacy Ruby and AWS SDK support.
20
+
21
+ ## Getting Started
22
+
23
+ In your `Gemfile`:
24
+
25
+ ```ruby
26
+ gem 'mail-ses'
27
+ ```
28
+
29
+ Next, make a new initializer at `config/initializers/mail_ses.rb`:
30
+
31
+ ```ruby
32
+ ActionMailer::Base.add_delivery_method :ses, Mail::SES,
33
+ region: 'us-east-1',
34
+ access_key_id: 'abc',
35
+ secret_access_key: '123'
36
+ ```
37
+
38
+ Finally, in the appropriate `config/environments/*.rb`:
39
+
40
+ ```ruby
41
+ config.action_mailer.delivery_method = :ses
42
+ ```
43
+
44
+ ## Advanced Usage
45
+
46
+ ### AWS SES Client Options
47
+
48
+ Any options supported by the `Aws::SESV2::Client` class can be passed into the initializer, for example:
49
+
50
+ ```ruby
51
+ ActionMailer::Base.add_delivery_method :ses, Mail::SES,
52
+ region: 'us-east-1',
53
+ session_token: 'foobar',
54
+ retry_limit: 5,
55
+ retry_max_delay: 10
56
+ ```
57
+
58
+ In addition, the shortcut option `:use_iam_profile (Boolean)` which activates the IAM instance profile.
59
+
60
+ ```ruby
61
+ ActionMailer::Base.add_delivery_method :ses, Mail::SES,
62
+ region: 'us-east-1',
63
+ use_iam_profile: true
64
+ ```
65
+
66
+ ### Default Mail Options
67
+
68
+ In the initializer you can set `:mail_options (Hash)` which are default options to pass-through to each mail sent:
69
+
70
+ ```ruby
71
+ ActionMailer::Base.add_delivery_method :ses, Mail::SES,
72
+ # ...
73
+ mail_options: {
74
+ from_email_address_identity_arn: 'arn:aws:ses:us-east-1:123456789012:identity/example.com',
75
+ email_tags: [
76
+ { name: 'MessageTagName', value: 'MessageTagValue' },
77
+ ],
78
+ }
79
+ ```
80
+
81
+ ### Override Mail Options
82
+
83
+ You can override the default mail options on a per-mail basis by passing them in the `mail` method:
84
+
85
+
86
+ ```ruby
87
+ class ApplicationMailer < ActionMailer::Base
88
+ def example
89
+ mail(
90
+ to: "foo@example.com",
91
+ from: "bar@example.com",
92
+ mail_options: {
93
+ email_tags: [
94
+ { name: 'MessageTagName', value: 'MessageTagValue' },
95
+ ],
96
+ }
97
+ )
98
+ end
99
+ end
100
+ ```
101
+
102
+ ### AWS Error Handling
103
+
104
+ To handle errors from AWS API, in the initializer you can set `:error_handler (Proc)` which takes two args:
105
+ the error which was raised, and the raw_email options hash. This is useful for notifying your bug tracking service.
106
+ Setting `:error_handler` causes the error to be swallowed unless it is raised again in the handler itself.
107
+
108
+ ```ruby
109
+ ActionMailer::Base.add_delivery_method :ses, Mail::SES,
110
+ # ...
111
+ error_handler: ->(error, raw_email) do
112
+ Bugsnag.notify(error){|r| r.add_tab('email', { email: raw_email })}
113
+ raise error
114
+ end
115
+ ```
116
+
117
+ ### Send Email as a Standalone
118
+
119
+ You can send one-off mails using the `Mail::SES` object and `#deliver` method.
120
+
121
+ ```ruby
122
+ mail = Mail.new(args)
123
+
124
+ ses = Mail::SES.new(region: 'us-east-1',
125
+ access_key_id: 'abc',
126
+ secret_access_key: '123')
127
+
128
+ options = { from_email_address_identity_arn: 'arn:aws:ses:us-east-1:123456789012:identity/example.com' }
129
+
130
+ ses.deliver!(mail, options) #=> returns AWS API response
131
+
132
+ mail.message_id #=> "00000138111222aa-33322211-cccc-cccc-cccc-ddddaaaa0680-000000@email.amazonses.com"
133
+ ```
134
+
135
+ Please also see the [AWS SDK v3 for SES](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/send-using-sdk-ruby.html) for alternate approaches.
136
+
137
+ ### Statistics, Verified Addresses, Bounce Rate, etc.
138
+
139
+ Please use the official [AWS SDK v3 for SES](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/send-using-sdk-ruby.html).
140
+
141
+ ## Copyright
142
+
143
+ Copyright (c) 2024 [Teamtailor](http://www.teamtailor.com/). See LICENSE for further details.
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mail
4
+ class SES
5
+ # Validates a Mail::Message object before sending
6
+ class MessageValidator
7
+ # message - The Mail::Message object to be validated.
8
+ def initialize(message)
9
+ @message = message
10
+ end
11
+
12
+ # Validate the message.
13
+ def validate
14
+ validate_class
15
+ validate_delivery_params
16
+ validate_attachments
17
+ end
18
+
19
+ private
20
+
21
+ def validate_class
22
+ return if @message.is_a?(Mail::Message)
23
+
24
+ raise ArgumentError.new("mail must be an instance of Mail::Message class")
25
+ end
26
+
27
+ def validate_delivery_params
28
+ Mail::SmtpEnvelope.new(@message)
29
+ end
30
+
31
+ def validate_attachments
32
+ return unless @message.has_attachments? && @message.text_part.nil? && @message.html_part.nil?
33
+
34
+ raise ArgumentError.new("Attachment provided without message body")
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mail
4
+ class SES
5
+ # Builds options for Aws::SESV2::Client#send_email
6
+ class OptionsBuilder
7
+ SES_FIELDS = %i[ from_email_address
8
+ from_email_address_identity_arn
9
+ reply_to_addresses
10
+ feedback_forwarding_email_address
11
+ feedback_forwarding_email_address_identity_arn
12
+ email_tags
13
+ configuration_set_name ].freeze
14
+
15
+ # message - The Mail::Message object to be sent.
16
+ # options - The Hash options which override any defaults
17
+ # from the message.
18
+ def initialize(message, options = {})
19
+ @message = message
20
+ @options = options
21
+ end
22
+
23
+ # Returns the options for Aws::SESV2::Client#send_email.
24
+ def build
25
+ message_options.merge(ses_options, ses_options_from_message)
26
+ end
27
+
28
+ private
29
+
30
+ def ses_options
31
+ # TODO: address fields should be encoded to UTF-8
32
+ slice_hash(@options, *SES_FIELDS)
33
+ end
34
+
35
+ def ses_options_from_message
36
+ mail_options = @message[:mail_options]&.unparsed_value
37
+
38
+ if mail_options
39
+ @message[:mail_options] = nil
40
+ slice_hash(mail_options, *SES_FIELDS)
41
+ else
42
+ {}
43
+ end
44
+ end
45
+
46
+ def message_options
47
+ {
48
+ from_email_address: extract_value(:from)&.first,
49
+ reply_to_addresses: extract_value(:reply_to),
50
+ destination: {
51
+ to_addresses: extract_value(:to) || [],
52
+ cc_addresses: extract_value(:cc) || [],
53
+ bcc_addresses: extract_value(:bcc) || []
54
+ },
55
+ content: {raw: {data: @message.to_s}}
56
+ }.compact
57
+ end
58
+
59
+ def slice_hash(hash, *keys)
60
+ keys.each_with_object({}) { |k, h| h[k] = hash[k] if hash.key?(k) }
61
+ end
62
+
63
+ def extract_value(key)
64
+ value = @message.header[key]
65
+ return unless value
66
+
67
+ if value.respond_to?(:formatted)
68
+ value.formatted
69
+ elsif value.respond_to?(:unparsed_value)
70
+ Array(value.unparsed_value)
71
+ else
72
+ []
73
+ end&.map { |v| encode(v) }
74
+ end
75
+
76
+ def encode(value)
77
+ Mail::Encodings.address_encode(value)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mail
4
+ class SES
5
+ VERSION = "1.1.0.pre"
6
+ end
7
+ end
data/lib/mail/ses.rb ADDED
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mail/ses/version"
4
+ require "mail/ses/message_validator"
5
+ require "mail/ses/options_builder"
6
+
7
+ module Mail
8
+ # Mail delivery method handler for AWS SES
9
+ class SES
10
+ attr_accessor :settings
11
+ attr_reader :client
12
+
13
+ # Initializes the Mail::SES object.
14
+ #
15
+ # options - The Hash options (optional, default: {}):
16
+ # :mail_options - (Hash) Default AWS options to set on each mail object.
17
+ # :error_handler - (Proc<Error, Hash>) Handler for AWS API errors.
18
+ # :use_iam_profile - Shortcut to use AWS IAM instance profile.
19
+ # All other options are passed-thru to Aws::SESV2::Client.
20
+ def initialize(options = {})
21
+ @mail_options = options.delete(:mail_options) || {}
22
+
23
+ @error_handler = options.delete(:error_handler)
24
+ raise ArgumentError.new(":error_handler must be a Proc") if @error_handler && !@error_handler.is_a?(Proc)
25
+
26
+ @settings = {
27
+ return_response: options.delete(:return_response),
28
+ message_id_domain: options.delete(:message_id_domain)
29
+ }
30
+
31
+ options[:credentials] = Aws::InstanceProfileCredentials.new if options.delete(:use_iam_profile)
32
+ @client = Aws::SESV2::Client.new(options)
33
+ end
34
+
35
+ # Delivers a Mail::Message object via SES.
36
+ #
37
+ # message - The Mail::Message object to deliver (required).
38
+ # options - The Hash options which override any defaults set in :mail_options
39
+ # in the initializer (optional, default: {}). Refer to
40
+ # Aws::SESV2::Client#send_email
41
+ def deliver!(message, options = {})
42
+ MessageValidator.new(message).validate
43
+
44
+ options = @mail_options.merge(options || {})
45
+ send_options = OptionsBuilder.new(message, options).build
46
+
47
+ begin
48
+ response = client.send_email(send_options)
49
+ message.message_id = "#{response.to_h[:message_id]}@#{settings[:message_id_domain] || "email.amazonses.com"}"
50
+ settings[:return_response] ? response : self
51
+ rescue => e
52
+ handle_error(e, send_options)
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def handle_error(error, send_options)
59
+ raise(error) unless @error_handler
60
+
61
+ @error_handler.call(error, send_options.dup)
62
+ end
63
+ end
64
+ end
data/lib/mail-ses.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aws-sdk-sesv2"
4
+ require "mail"
5
+ require "mail/ses"
metadata ADDED
@@ -0,0 +1,149 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tt-mail-ses
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0.pre
5
+ platform: ruby
6
+ authors:
7
+ - Jonas Brusman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-10-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk-sesv2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.27'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '1.27'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mail
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.8.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 2.8.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: net-smtp
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: nokogiri
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '1'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '3.8'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '3.8'
97
+ - !ruby/object:Gem::Dependency
98
+ name: standard
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '='
102
+ - !ruby/object:Gem::Version
103
+ version: 1.41.1
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '='
109
+ - !ruby/object:Gem::Version
110
+ version: 1.41.1
111
+ description: Ruby Mail delivery method handler for Amazon SES
112
+ email: platform@teamtailor.com
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - CHANGELOG.md
118
+ - LICENSE
119
+ - README.md
120
+ - lib/mail-ses.rb
121
+ - lib/mail/ses.rb
122
+ - lib/mail/ses/message_validator.rb
123
+ - lib/mail/ses/options_builder.rb
124
+ - lib/mail/ses/version.rb
125
+ homepage: https://github.com/teamtailor/mail-ses
126
+ licenses:
127
+ - MIT
128
+ metadata:
129
+ rubygems_mfa_required: 'true'
130
+ post_install_message:
131
+ rdoc_options: []
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: 3.0.0
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">"
142
+ - !ruby/object:Gem::Version
143
+ version: 1.3.1
144
+ requirements: []
145
+ rubygems_version: 3.4.10
146
+ signing_key:
147
+ specification_version: 4
148
+ summary: Ruby Mail delivery method handler for Amazon SES
149
+ test_files: []