mailgun-ruby 1.1.0 → 1.2.5
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 +5 -5
- data/.gitignore +1 -0
- data/.ruby-env.yml.example +1 -1
- data/.travis.yml +23 -7
- data/Gemfile +2 -0
- data/README.md +79 -18
- data/{Domains.md → docs/Domains.md} +18 -0
- data/{Events.md → docs/Events.md} +0 -0
- data/{MessageBuilder.md → docs/MessageBuilder.md} +32 -13
- data/{Messages.md → docs/Messages.md} +3 -3
- data/{OptInHandler.md → docs/OptInHandler.md} +0 -0
- data/{Snippets.md → docs/Snippets.md} +21 -2
- data/docs/Suppressions.md +82 -0
- data/{Webhooks.md → docs/Webhooks.md} +1 -1
- data/docs/railgun/Overview.md +11 -0
- data/docs/railgun/Parameters.md +83 -0
- data/lib/mailgun/address.rb +48 -0
- data/lib/mailgun/client.rb +85 -8
- data/lib/mailgun/events/events.rb +40 -12
- data/lib/mailgun/exceptions/exceptions.rb +21 -7
- data/lib/mailgun/lists/opt_in_handler.rb +0 -1
- data/lib/mailgun/messages/batch_message.rb +3 -2
- data/lib/mailgun/messages/message_builder.rb +139 -30
- data/lib/mailgun/response.rb +7 -0
- data/lib/mailgun/suppressions.rb +273 -0
- data/lib/mailgun/version.rb +1 -1
- data/lib/mailgun/webhooks/webhooks.rb +1 -1
- data/lib/mailgun-ruby.rb +2 -0
- data/lib/mailgun.rb +1 -0
- data/lib/railgun/attachment.rb +56 -0
- data/lib/railgun/errors.rb +27 -0
- data/lib/railgun/mailer.rb +253 -0
- data/lib/railgun/message.rb +18 -0
- data/lib/railgun/railtie.rb +10 -0
- data/lib/railgun.rb +8 -0
- data/mailgun.gemspec +12 -13
- data/spec/integration/email_validation_spec.rb +57 -15
- data/spec/integration/events_spec.rb +9 -1
- data/spec/integration/mailer_spec.rb +67 -0
- data/spec/integration/mailgun_spec.rb +51 -1
- data/spec/integration/suppressions_spec.rb +142 -0
- data/spec/spec_helper.rb +3 -1
- data/spec/unit/connection/test_client.rb +16 -0
- data/spec/unit/events/events_spec.rb +36 -2
- data/spec/unit/mailgun_spec.rb +32 -10
- data/spec/unit/messages/batch_message_spec.rb +56 -40
- data/spec/unit/messages/message_builder_spec.rb +267 -81
- data/spec/unit/messages/sample_data/unknown.type +0 -0
- data/spec/unit/railgun/content_type_spec.rb +71 -0
- data/spec/unit/railgun/mailer_spec.rb +388 -0
- data/vcr_cassettes/email_validation.yml +136 -25
- data/vcr_cassettes/events.yml +48 -1
- data/vcr_cassettes/exceptions.yml +45 -0
- data/vcr_cassettes/mailer_invalid_domain.yml +109 -0
- data/vcr_cassettes/message_deliver.yml +149 -0
- data/vcr_cassettes/suppressions.yml +727 -0
- metadata +65 -40
data/lib/mailgun/response.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
1
3
|
module Mailgun
|
2
4
|
# A Mailgun::Response object is instantiated for each response generated
|
3
5
|
# by the Client request. The Response object supports deserialization of
|
@@ -10,6 +12,11 @@ module Mailgun
|
|
10
12
|
# slightly different
|
11
13
|
attr_accessor :body, :code
|
12
14
|
|
15
|
+
def self.from_hash(h)
|
16
|
+
# Create a "fake" response object with the data passed from h
|
17
|
+
self.new OpenStruct.new(h)
|
18
|
+
end
|
19
|
+
|
13
20
|
def initialize(response)
|
14
21
|
@body = response.body
|
15
22
|
@code = response.code
|
@@ -0,0 +1,273 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
require 'mailgun/exceptions/exceptions'
|
4
|
+
|
5
|
+
module Mailgun
|
6
|
+
|
7
|
+
# The Mailgun::Suppressions object makes it easy to manage "suppressions"
|
8
|
+
# attached to an account. "Suppressions" means bounces, unsubscribes, and complaints.
|
9
|
+
class Suppressions
|
10
|
+
|
11
|
+
# @param [Mailgun::Client] client API client to use for requests
|
12
|
+
# @param [String] domain Domain name to use for the suppression endpoints.
|
13
|
+
def initialize(client, domain)
|
14
|
+
@client = client
|
15
|
+
@domain = domain
|
16
|
+
|
17
|
+
@paging_next = nil
|
18
|
+
@paging_prev = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
####
|
22
|
+
# Paging operations
|
23
|
+
####
|
24
|
+
|
25
|
+
def next
|
26
|
+
response = get_from_paging @paging_next[:path], @paging_next[:params]
|
27
|
+
extract_paging response
|
28
|
+
response
|
29
|
+
end
|
30
|
+
|
31
|
+
def prev
|
32
|
+
response = get_from_paging @paging_prev[:path], @paging_prev[:params]
|
33
|
+
extract_paging response
|
34
|
+
response
|
35
|
+
end
|
36
|
+
|
37
|
+
####
|
38
|
+
# Bounces Endpoint (/v3/:domain/bounces)
|
39
|
+
####
|
40
|
+
|
41
|
+
def list_bounces(params = {})
|
42
|
+
response = @client.get("#{@domain}/bounces", params)
|
43
|
+
extract_paging response
|
44
|
+
response
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_bounce(address)
|
48
|
+
@client.get("#{@domain}/bounces/#{address}", nil)
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_bounce(params = {})
|
52
|
+
@client.post("#{@domain/bounces}", params)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Creates multiple bounces on the Mailgun API.
|
56
|
+
# If a bounce does not have a valid structure, it will be added to a list of unsendable bounces.
|
57
|
+
# The list of unsendable bounces will be returned at the end of this operation.
|
58
|
+
#
|
59
|
+
# If more than 999 bounce entries are provided, the list will be split and recursive calls will be made.
|
60
|
+
#
|
61
|
+
# @param [Array] data Array of bounce hashes
|
62
|
+
# @return [Response] Mailgun API response
|
63
|
+
# @return [Array] Return values from recursive call for list split.
|
64
|
+
def create_bounces(data)
|
65
|
+
# `data` should be a list of hashes, with each hash containing *at least* an `address` key.
|
66
|
+
split_return = []
|
67
|
+
if data.length >= 1000 then
|
68
|
+
resp, resp_l = create_bounces data[999..-1]
|
69
|
+
split_return.push(resp)
|
70
|
+
split_return.concat(resp_l)
|
71
|
+
data = data[0..998]
|
72
|
+
elsif data.length == 0 then
|
73
|
+
return nil, []
|
74
|
+
end
|
75
|
+
|
76
|
+
valid = []
|
77
|
+
# Validate the bounces given
|
78
|
+
# NOTE: `data` could potentially be very large (1000 elements) so it is
|
79
|
+
# more efficient to pop from data and push into a different array as
|
80
|
+
# opposed to possibly copying the entire array to another array.
|
81
|
+
while not data.empty? do
|
82
|
+
bounce = data.pop
|
83
|
+
# Bounces MUST contain a `address` key.
|
84
|
+
if not bounce.include? :address then
|
85
|
+
raise Mailgun::ParameterError.new "Bounce MUST include a :address key: #{bounce}"
|
86
|
+
end
|
87
|
+
|
88
|
+
bounce.each do |k, v|
|
89
|
+
# Hash values MUST be strings.
|
90
|
+
if not v.is_a? String then
|
91
|
+
bounce[k] = v.to_s
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
valid.push bounce
|
96
|
+
end
|
97
|
+
|
98
|
+
response = @client.post("#{@domain}/bounces", valid.to_json, { "Content-Type" => "application/json" })
|
99
|
+
return response, split_return
|
100
|
+
end
|
101
|
+
|
102
|
+
def delete_bounce(address)
|
103
|
+
@client.delete("#{@domain}/bounces/#{address}")
|
104
|
+
end
|
105
|
+
|
106
|
+
def delete_all_bounces
|
107
|
+
@client.delete("#{@domain}/bounces")
|
108
|
+
end
|
109
|
+
|
110
|
+
####
|
111
|
+
# Unsubscribes Endpoint (/v3/:domain/unsubscribes)
|
112
|
+
####
|
113
|
+
|
114
|
+
def list_unsubscribes(params = {})
|
115
|
+
response = @client.get("#{@domain}/unsubscribes", params)
|
116
|
+
extract_paging response
|
117
|
+
response
|
118
|
+
end
|
119
|
+
|
120
|
+
def get_unsubscribe(address)
|
121
|
+
@client.get("#{@domain}/unsubscribes/#{address}")
|
122
|
+
end
|
123
|
+
|
124
|
+
def create_unsubscribe(params = {})
|
125
|
+
@client.post("#{@domain}/unsubscribes", params)
|
126
|
+
end
|
127
|
+
|
128
|
+
# Creates multiple unsubscribes on the Mailgun API.
|
129
|
+
# If an unsubscribe does not have a valid structure, it will be added to a list of unsendable unsubscribes.
|
130
|
+
# The list of unsendable unsubscribes will be returned at the end of this operation.
|
131
|
+
#
|
132
|
+
# If more than 999 unsubscribe entries are provided, the list will be split and recursive calls will be made.
|
133
|
+
#
|
134
|
+
# @param [Array] data Array of unsubscribe hashes
|
135
|
+
# @return [Response] Mailgun API response
|
136
|
+
# @return [Array] Return values from recursive call for list split.
|
137
|
+
def create_unsubscribes(data)
|
138
|
+
# `data` should be a list of hashes, with each hash containing *at least* an `address` key.
|
139
|
+
split_return = []
|
140
|
+
if data.length >= 1000 then
|
141
|
+
resp, resp_l = create_unsubscribes data[999..-1]
|
142
|
+
split_return.push(resp)
|
143
|
+
split_return.concat(resp_l)
|
144
|
+
data = data[0..998]
|
145
|
+
elsif data.length == 0 then
|
146
|
+
return nil, []
|
147
|
+
end
|
148
|
+
|
149
|
+
valid = []
|
150
|
+
# Validate the unsubscribes given
|
151
|
+
while not data.empty? do
|
152
|
+
unsubscribe = data.pop
|
153
|
+
# unsubscribes MUST contain a `address` key.
|
154
|
+
if not unsubscribe.include? :address then
|
155
|
+
raise Mailgun::ParameterError.new "Unsubscribe MUST include a :address key: #{unsubscribe}"
|
156
|
+
end
|
157
|
+
|
158
|
+
unsubscribe.each do |k, v|
|
159
|
+
# Hash values MUST be strings.
|
160
|
+
# However, unsubscribes contain an array of tags
|
161
|
+
if v.is_a? Array
|
162
|
+
unsubscribe[k] = v.map(&:to_s)
|
163
|
+
elsif !v.is_a? String
|
164
|
+
unsubscribe[k] = v.to_s
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
valid.push unsubscribe
|
169
|
+
end
|
170
|
+
|
171
|
+
response = @client.post("#{@domain}/unsubscribes", valid.to_json, { "Content-Type" => "application/json" })
|
172
|
+
return response, split_return
|
173
|
+
end
|
174
|
+
|
175
|
+
def delete_unsubscribe(address, params = {})
|
176
|
+
@client.delete("#{@domain}/unsubscribes/#{address}")
|
177
|
+
end
|
178
|
+
|
179
|
+
####
|
180
|
+
# Complaints Endpoint (/v3/:domain/complaints)
|
181
|
+
####
|
182
|
+
|
183
|
+
def list_complaints(params = {})
|
184
|
+
response = @client.get("#{@domain}/complaints", params)
|
185
|
+
extract_paging response
|
186
|
+
response
|
187
|
+
end
|
188
|
+
|
189
|
+
def get_complaint(address)
|
190
|
+
@client.get("#{@domain}/complaints/#{address}", nil)
|
191
|
+
end
|
192
|
+
|
193
|
+
def create_complaint(params = {})
|
194
|
+
@client.post("#{@domain}/complaints", params)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Creates multiple complaints on the Mailgun API.
|
198
|
+
# If a complaint does not have a valid structure, it will be added to a list of unsendable complaints.
|
199
|
+
# The list of unsendable complaints will be returned at the end of this operation.
|
200
|
+
#
|
201
|
+
# If more than 999 complaint entries are provided, the list will be split and recursive calls will be made.
|
202
|
+
#
|
203
|
+
# @param [Array] data Array of complaint hashes
|
204
|
+
# @return [Response] Mailgun API response
|
205
|
+
# @return [Array] Return values from recursive call for list split.
|
206
|
+
def create_complaints(data)
|
207
|
+
# `data` should be a list of hashes, with each hash containing *at least* an `address` key.
|
208
|
+
split_return = []
|
209
|
+
if data.length >= 1000 then
|
210
|
+
resp, resp_l = create_complaints data[999..-1]
|
211
|
+
split_return.push(resp)
|
212
|
+
split_return.concat(resp_l)
|
213
|
+
data = data[0..998]
|
214
|
+
elsif data.length == 0 then
|
215
|
+
return nil, []
|
216
|
+
end
|
217
|
+
|
218
|
+
valid = []
|
219
|
+
# Validate the complaints given
|
220
|
+
while not data.empty? do
|
221
|
+
complaint = data.pop
|
222
|
+
# complaints MUST contain a `address` key.
|
223
|
+
if not complaint.include? :address then
|
224
|
+
raise Mailgun::ParameterError.new "Complaint MUST include a :address key: #{complaint}"
|
225
|
+
end
|
226
|
+
|
227
|
+
complaint.each do |k, v|
|
228
|
+
# Hash values MUST be strings.
|
229
|
+
if not v.is_a? String then
|
230
|
+
complaint[k] = v.to_s
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
valid.push complaint
|
235
|
+
end
|
236
|
+
|
237
|
+
response = @client.post("#{@domain}/complaints", valid.to_json, { "Content-Type" => "application/json" })
|
238
|
+
return response, split_return
|
239
|
+
end
|
240
|
+
|
241
|
+
def delete_complaint(address)
|
242
|
+
@client.delete("#{@domain}/complaints/#{address}")
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
def get_from_paging(uri, params = {})
|
248
|
+
@client.get(uri, params)
|
249
|
+
end
|
250
|
+
|
251
|
+
def extract_paging(response)
|
252
|
+
rhash = response.to_h
|
253
|
+
return nil unless rhash.include? "paging"
|
254
|
+
|
255
|
+
page_info = rhash["paging"]
|
256
|
+
|
257
|
+
# Build the `next` endpoint
|
258
|
+
page_next = URI.parse(page_info["next"])
|
259
|
+
@paging_next = {
|
260
|
+
:path => page_next.path[/\/v[\d](.+)/, 1],
|
261
|
+
:params => Hash[URI.decode_www_form page_next.query],
|
262
|
+
}
|
263
|
+
|
264
|
+
# Build the `prev` endpoint
|
265
|
+
page_prev = URI.parse(page_info["previous"])
|
266
|
+
@paging_prev = {
|
267
|
+
:path => page_prev.path[/\/v[\d](.+)/, 1],
|
268
|
+
:params => Hash[URI.decode_www_form page_prev.query],
|
269
|
+
}
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
273
|
+
end
|
data/lib/mailgun/version.rb
CHANGED
@@ -46,7 +46,7 @@ module Mailgun
|
|
46
46
|
# Returns a Boolean of whether the webhook was created
|
47
47
|
def create(domain, action, url = '')
|
48
48
|
res = @client.post("domains/#{domain}/webhooks", id: action, url: url)
|
49
|
-
res.to_h['webhook'] == url && res.to_h[message] == 'Webhook has been created'
|
49
|
+
res.to_h['webhook']['url'] == url && res.to_h['message'] == 'Webhook has been created'
|
50
50
|
end
|
51
51
|
alias_method :add, :create
|
52
52
|
alias_method :add_webhook, :create
|
data/lib/mailgun-ruby.rb
ADDED
data/lib/mailgun.rb
CHANGED
@@ -7,6 +7,7 @@ require 'mailgun/version'
|
|
7
7
|
require 'mailgun/client'
|
8
8
|
require 'mailgun/response'
|
9
9
|
require 'mailgun/chains'
|
10
|
+
require 'mailgun/address'
|
10
11
|
require 'mailgun/lists/opt_in_handler'
|
11
12
|
require 'mailgun/messages/batch_message'
|
12
13
|
require 'mailgun/messages/message_builder'
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Railgun
|
2
|
+
|
3
|
+
class Attachment < StringIO
|
4
|
+
|
5
|
+
attr_reader :filename, :content_type, :path,
|
6
|
+
:original_filename, :overwritten_filename
|
7
|
+
|
8
|
+
def initialize(attachment, *args)
|
9
|
+
@path = ''
|
10
|
+
@inline = args.detect { |opt| opt[:inline] }
|
11
|
+
|
12
|
+
if @inline
|
13
|
+
@filename = attachment.cid
|
14
|
+
else
|
15
|
+
@filename = attachment.filename
|
16
|
+
end
|
17
|
+
|
18
|
+
@original_filename = @filename
|
19
|
+
|
20
|
+
if args.detect { |opt| opt[:filename] }
|
21
|
+
@filename = opt[:filename]
|
22
|
+
end
|
23
|
+
|
24
|
+
@overwritten_filename = @filename
|
25
|
+
|
26
|
+
@content_type = attachment.content_type.split(';')[0]
|
27
|
+
|
28
|
+
super attachment.body.decoded
|
29
|
+
end
|
30
|
+
|
31
|
+
def inline?
|
32
|
+
@inline
|
33
|
+
end
|
34
|
+
|
35
|
+
def is_original_filename
|
36
|
+
@original_filename == @overwritten_filename
|
37
|
+
end
|
38
|
+
|
39
|
+
def source_filename
|
40
|
+
@filename
|
41
|
+
end
|
42
|
+
|
43
|
+
def attach_to_message!(mb)
|
44
|
+
if mb.nil?
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
if inline?
|
49
|
+
mb.add_inline_image self, @filename
|
50
|
+
else
|
51
|
+
mb.add_attachment self, @filename
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Railgun
|
2
|
+
|
3
|
+
class Error < StandardError
|
4
|
+
|
5
|
+
attr_reader :object
|
6
|
+
|
7
|
+
def initialize(message = nil, object = nil)
|
8
|
+
super(message)
|
9
|
+
|
10
|
+
@object = object
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class ConfigurationError < Error
|
15
|
+
end
|
16
|
+
|
17
|
+
class InternalError < Error
|
18
|
+
|
19
|
+
attr_reader :source_exception
|
20
|
+
|
21
|
+
def initialize(source_exc, message = nil, object = nil)
|
22
|
+
super(message, object)
|
23
|
+
|
24
|
+
@source_exception = source_exc
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,253 @@
|
|
1
|
+
require 'action_mailer'
|
2
|
+
require 'json'
|
3
|
+
require 'mailgun'
|
4
|
+
require 'rails'
|
5
|
+
require 'railgun/errors'
|
6
|
+
|
7
|
+
module Railgun
|
8
|
+
|
9
|
+
# Railgun::Mailer is an ActionMailer provider for sending mail through
|
10
|
+
# Mailgun.
|
11
|
+
class Mailer
|
12
|
+
|
13
|
+
# List of the headers that will be ignored when copying headers from `mail.header_fields`
|
14
|
+
IGNORED_HEADERS = %w[ to from subject reply-to mime-version template ]
|
15
|
+
|
16
|
+
# [Hash] config ->
|
17
|
+
# Requires *at least* `api_key` and `domain` keys.
|
18
|
+
attr_accessor :config, :domain, :settings
|
19
|
+
|
20
|
+
# Initialize the Railgun mailer.
|
21
|
+
#
|
22
|
+
# @param [Hash] config Hash of config values, typically from `app_config.action_mailer.mailgun_config`
|
23
|
+
def initialize(config)
|
24
|
+
@config = config
|
25
|
+
|
26
|
+
[:api_key, :domain].each do |k|
|
27
|
+
raise Railgun::ConfigurationError.new("Config requires `#{k}` key", @config) unless @config.has_key?(k)
|
28
|
+
end
|
29
|
+
|
30
|
+
@mg_client = Mailgun::Client.new(
|
31
|
+
config[:api_key],
|
32
|
+
config[:api_host] || 'api.mailgun.net',
|
33
|
+
config[:api_version] || 'v3',
|
34
|
+
config[:api_ssl].nil? ? true : config[:api_ssl],
|
35
|
+
false,
|
36
|
+
config[:timeout],
|
37
|
+
)
|
38
|
+
@domain = @config[:domain]
|
39
|
+
|
40
|
+
# To avoid exception in mail gem v2.6
|
41
|
+
@settings = { return_response: true }
|
42
|
+
|
43
|
+
if (@config[:fake_message_send] || false)
|
44
|
+
Rails.logger.info "NOTE: fake message sending has been enabled for mailgun-ruby!"
|
45
|
+
@mg_client.enable_test_mode!
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def deliver!(mail)
|
50
|
+
@mg_domain = set_mg_domain(mail)
|
51
|
+
mail[:domain] = nil if mail[:domain].present?
|
52
|
+
|
53
|
+
mg_message = Railgun.transform_for_mailgun(mail)
|
54
|
+
response = @mg_client.send_message(@mg_domain, mg_message)
|
55
|
+
|
56
|
+
if response.code == 200 then
|
57
|
+
mg_id = response.to_h['id']
|
58
|
+
mail.message_id = mg_id
|
59
|
+
end
|
60
|
+
response
|
61
|
+
end
|
62
|
+
|
63
|
+
def mailgun_client
|
64
|
+
@mg_client
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# Set @mg_domain from mail[:domain] header if present, then remove it to prevent being sent.
|
70
|
+
def set_mg_domain(mail)
|
71
|
+
return mail[:domain].value if mail[:domain]
|
72
|
+
domain
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
module_function
|
78
|
+
|
79
|
+
# Performs a series of transformations on the `mailgun*` attributes.
|
80
|
+
# After prefixing them with the proper option type, they are added to
|
81
|
+
# the message hash where they will then be sent to the API as JSON.
|
82
|
+
#
|
83
|
+
# It is important to note that headers set in `mailgun_headers` on the message
|
84
|
+
# WILL overwrite headers set via `mail.headers()`.
|
85
|
+
#
|
86
|
+
# @param [Mail::Message] mail message to transform
|
87
|
+
#
|
88
|
+
# @return [Hash] transformed message hash
|
89
|
+
def transform_for_mailgun(mail)
|
90
|
+
message = build_message_object(mail)
|
91
|
+
|
92
|
+
# o:* attributes (options)
|
93
|
+
mail.mailgun_options.try(:each) do |k, v|
|
94
|
+
message["o:#{k}"] = v.dup
|
95
|
+
end
|
96
|
+
|
97
|
+
# t:* attributes (options)
|
98
|
+
mail.mailgun_template_variables.try(:each) do |k, v|
|
99
|
+
message["t:#{k}"] = v.dup
|
100
|
+
end
|
101
|
+
|
102
|
+
# support for using ActionMailer's `headers()` inside of the mailer
|
103
|
+
# note: this will filter out parameters such as `from`, `to`, and so forth
|
104
|
+
# as they are accepted as POST parameters on the message endpoint.
|
105
|
+
|
106
|
+
msg_headers = Hash.new
|
107
|
+
|
108
|
+
# h:* attributes (headers)
|
109
|
+
|
110
|
+
# Let's set all of these headers on the [Mail::Message] so that
|
111
|
+
# the are created inside of a [Mail::Header] instance and processed there.
|
112
|
+
mail.headers(mail.mailgun_headers || {})
|
113
|
+
mail.header_fields.each do |field|
|
114
|
+
header = field.name.downcase
|
115
|
+
if msg_headers.include? header
|
116
|
+
msg_headers[header] = [msg_headers[header], field.value].flatten
|
117
|
+
else
|
118
|
+
msg_headers[header] = field.value
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
msg_headers.each do |k, v|
|
123
|
+
if Railgun::Mailer::IGNORED_HEADERS.include? k.downcase
|
124
|
+
Rails.logger.debug("[railgun] ignoring header (using envelope instead): #{k}")
|
125
|
+
next
|
126
|
+
end
|
127
|
+
|
128
|
+
# Cover cases like `cc`, `bcc` where parameters are valid
|
129
|
+
# headers BUT they are submitted as separate POST params
|
130
|
+
# and already exist on the message because of the call to
|
131
|
+
# `build_message_object`.
|
132
|
+
if message.include? k.downcase
|
133
|
+
Rails.logger.debug("[railgun] ignoring header (already set): #{k}")
|
134
|
+
next
|
135
|
+
end
|
136
|
+
|
137
|
+
message["h:#{k}"] = v
|
138
|
+
end
|
139
|
+
|
140
|
+
# recipient variables
|
141
|
+
message['recipient-variables'] = mail.mailgun_recipient_variables.to_json if mail.mailgun_recipient_variables
|
142
|
+
|
143
|
+
# reject blank values
|
144
|
+
message.delete_if do |k, v|
|
145
|
+
return true if v.nil?
|
146
|
+
|
147
|
+
# if it's an array remove empty elements
|
148
|
+
v.delete_if { |i| i.respond_to?(:empty?) && i.empty? } if v.is_a?(Array)
|
149
|
+
|
150
|
+
v.respond_to?(:empty?) && v.empty?
|
151
|
+
end
|
152
|
+
|
153
|
+
return message
|
154
|
+
end
|
155
|
+
|
156
|
+
# Acts on a Rails/ActionMailer message object and uses Mailgun::MessageBuilder
|
157
|
+
# to construct a new message.
|
158
|
+
#
|
159
|
+
# @param [Mail::Message] mail message to transform
|
160
|
+
#
|
161
|
+
# @returns [Hash] Message hash from Mailgun::MessageBuilder
|
162
|
+
def build_message_object(mail)
|
163
|
+
mb = Mailgun::MessageBuilder.new
|
164
|
+
|
165
|
+
mb.from mail[:from]
|
166
|
+
mb.reply_to(mail[:reply_to].to_s) if mail[:reply_to].present?
|
167
|
+
mb.template(mail[:template].to_s) if mail[:template].present?
|
168
|
+
mb.subject mail.subject
|
169
|
+
mb.body_html extract_body_html(mail)
|
170
|
+
mb.body_text extract_body_text(mail)
|
171
|
+
|
172
|
+
[:to, :cc, :bcc].each do |rcpt_type|
|
173
|
+
addrs = mail[rcpt_type] || nil
|
174
|
+
case addrs
|
175
|
+
when String
|
176
|
+
# Likely a single recipient
|
177
|
+
mb.add_recipient rcpt_type.to_s, addrs
|
178
|
+
when Array
|
179
|
+
addrs.each do |addr|
|
180
|
+
mb.add_recipient rcpt_type.to_s, addr
|
181
|
+
end
|
182
|
+
when Mail::Field
|
183
|
+
mb.add_recipient rcpt_type.to_s, addrs.to_s
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# v:* attributes (variables)
|
188
|
+
mail.mailgun_variables.try(:each) do |name, value|
|
189
|
+
mb.variable(name, value)
|
190
|
+
end
|
191
|
+
|
192
|
+
return mb.message if mail.attachments.empty?
|
193
|
+
|
194
|
+
mail.attachments.each do |attach|
|
195
|
+
attach = Attachment.new(attach, encoding: 'ascii-8bit', inline: attach.inline?)
|
196
|
+
attach.attach_to_message! mb
|
197
|
+
end
|
198
|
+
|
199
|
+
return mb.message
|
200
|
+
end
|
201
|
+
|
202
|
+
# Returns the decoded HTML body from the Mail::Message object if available,
|
203
|
+
# otherwise nil.
|
204
|
+
#
|
205
|
+
# @param [Mail::Message] mail message to transform
|
206
|
+
#
|
207
|
+
# @return [String]
|
208
|
+
def extract_body_html(mail)
|
209
|
+
begin
|
210
|
+
retrieve_html_part(mail).body.decoded || nil
|
211
|
+
rescue
|
212
|
+
nil
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Returns the decoded text body from the Mail::Message object if it is available,
|
217
|
+
# otherwise nil.
|
218
|
+
#
|
219
|
+
# @param [Mail::Message] mail message to transform
|
220
|
+
#
|
221
|
+
# @return [String]
|
222
|
+
def extract_body_text(mail)
|
223
|
+
begin
|
224
|
+
retrieve_text_part(mail).body.decoded || nil
|
225
|
+
rescue
|
226
|
+
nil
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Returns the mail object from the Mail::Message object if text part exists,
|
231
|
+
# (decomposing multipart into individual format if necessary)
|
232
|
+
# otherwise nil.
|
233
|
+
#
|
234
|
+
# @param [Mail::Message] mail message to transform
|
235
|
+
#
|
236
|
+
# @return [Mail::Message] mail message with its content-type = text/plain
|
237
|
+
def retrieve_text_part(mail)
|
238
|
+
return mail.text_part if mail.multipart?
|
239
|
+
(mail.mime_type =~ /^text\/plain$/i) && mail
|
240
|
+
end
|
241
|
+
|
242
|
+
# Returns the mail object from the Mail::Message object if html part exists,
|
243
|
+
# (decomposing multipart into individual format if necessary)
|
244
|
+
# otherwise nil.
|
245
|
+
#
|
246
|
+
# @param [Mail::Message] mail message to transform
|
247
|
+
#
|
248
|
+
# @return [Mail::Message] mail message with its content-type = text/html
|
249
|
+
def retrieve_html_part(mail)
|
250
|
+
return mail.html_part if mail.multipart?
|
251
|
+
(mail.mime_type =~ /^text\/html$/i) && mail
|
252
|
+
end
|
253
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'mail'
|
2
|
+
require 'mailgun/messages/message_builder'
|
3
|
+
require 'railgun/attachment'
|
4
|
+
require 'railgun/errors'
|
5
|
+
|
6
|
+
module Mail
|
7
|
+
|
8
|
+
class Message
|
9
|
+
|
10
|
+
# Attributes to hold Mailgun-specific information
|
11
|
+
attr_accessor :mailgun_variables,
|
12
|
+
:mailgun_options,
|
13
|
+
:mailgun_recipient_variables,
|
14
|
+
:mailgun_headers,
|
15
|
+
:mailgun_template_variables
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|