sendgrid-actionmailer 0.2.1 → 2.0.0
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/.travis.yml +7 -5
- data/Appraisals +4 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +5 -0
- data/README.md +178 -67
- data/lib/sendgrid_actionmailer/version.rb +1 -1
- data/lib/sendgrid_actionmailer.rb +189 -101
- data/sendgrid-actionmailer.gemspec +5 -4
- data/spec/lib/sendgrid_actionmailer_spec.rb +284 -268
- metadata +27 -29
- data/gemfiles/mail_2.5.gemfile.lock +0 -71
- data/gemfiles/mail_2.6.gemfile.lock +0 -66
@@ -1,136 +1,224 @@
|
|
1
1
|
require 'sendgrid_actionmailer/version'
|
2
2
|
require 'sendgrid_actionmailer/railtie' if defined? Rails
|
3
|
-
|
4
|
-
require 'fileutils'
|
5
|
-
require 'tmpdir'
|
6
|
-
|
7
3
|
require 'sendgrid-ruby'
|
8
4
|
|
9
5
|
module SendGridActionMailer
|
10
6
|
class DeliveryMethod
|
11
|
-
|
7
|
+
# TODO: use custom class to customer excpetion payload
|
8
|
+
SendgridDeliveryError = Class.new(StandardError)
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
10
|
+
include SendGrid
|
11
|
+
|
12
|
+
DEFAULTS = {
|
13
|
+
raise_delivery_errors: false,
|
14
|
+
}
|
15
|
+
|
16
|
+
attr_accessor :settings
|
17
|
+
|
18
|
+
def initialize(**params)
|
19
|
+
self.settings = DEFAULTS.merge(params)
|
18
20
|
end
|
19
21
|
|
20
22
|
def deliver!(mail)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
m.
|
27
|
-
|
28
|
-
m.from = from.address
|
29
|
-
m.from_name = from.display_name
|
30
|
-
m.reply_to = mail[:reply_to].addresses.first if mail[:reply_to]
|
31
|
-
m.date = mail[:date].to_s if mail[:date]
|
32
|
-
m.subject = mail.subject
|
33
|
-
end
|
34
|
-
|
35
|
-
smtpapi = mail['X-SMTPAPI']
|
36
|
-
|
37
|
-
# If multiple X-SMTPAPI headers are present on the message, then pick the
|
38
|
-
# first one. This may happen when X-SMTPAPI is set with defaults at the
|
39
|
-
# class-level (using defaults()), as well as inside an individual method
|
40
|
-
# (using headers[]=). In this case, we'll defer to the more specific
|
41
|
-
# header set in the individual method, which is the first header
|
42
|
-
# (somewhat counter-intuitively:
|
43
|
-
# https://github.com/rails/rails/issues/15912).
|
44
|
-
if(smtpapi.kind_of?(Array))
|
45
|
-
smtpapi = smtpapi.first
|
46
|
-
end
|
47
|
-
|
48
|
-
if smtpapi && smtpapi.value
|
49
|
-
begin
|
50
|
-
data = JSON.parse(smtpapi.value)
|
51
|
-
|
52
|
-
if data['filters']
|
53
|
-
email.smtpapi.set_filters(data['filters'])
|
54
|
-
end
|
23
|
+
sendgrid_mail = Mail.new.tap do |m|
|
24
|
+
m.from = to_email(mail.from)
|
25
|
+
m.reply_to = to_email(mail.reply_to)
|
26
|
+
m.subject = mail.subject
|
27
|
+
# https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/personalizations.html
|
28
|
+
m.add_personalization(to_personalizations(mail))
|
29
|
+
end
|
55
30
|
|
56
|
-
|
57
|
-
|
58
|
-
|
31
|
+
add_content(sendgrid_mail, mail)
|
32
|
+
add_send_options(sendgrid_mail, mail)
|
33
|
+
add_mail_settings(sendgrid_mail, mail)
|
34
|
+
add_tracking_settings(sendgrid_mail, mail)
|
59
35
|
|
60
|
-
|
61
|
-
email.smtpapi.set_send_at(data['send_at'])
|
62
|
-
end
|
36
|
+
response = perform_send_request(sendgrid_mail)
|
63
37
|
|
64
|
-
|
65
|
-
|
66
|
-
end
|
38
|
+
settings[:return_response] ? response : self
|
39
|
+
end
|
67
40
|
|
68
|
-
|
69
|
-
email.smtpapi.set_sections(data['section'])
|
70
|
-
end
|
41
|
+
private
|
71
42
|
|
72
|
-
|
73
|
-
|
74
|
-
|
43
|
+
def client
|
44
|
+
@client ||= SendGrid::API.new(api_key: settings.fetch(:api_key)).client
|
45
|
+
end
|
75
46
|
|
76
|
-
|
77
|
-
|
78
|
-
|
47
|
+
# type should be either :plain or :html
|
48
|
+
def to_content(type, value)
|
49
|
+
Content.new(type: "text/#{type}", value: value)
|
50
|
+
end
|
79
51
|
|
80
|
-
|
81
|
-
|
82
|
-
|
52
|
+
def to_email(input)
|
53
|
+
to_emails(input).first
|
54
|
+
end
|
83
55
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
56
|
+
def to_emails(input)
|
57
|
+
if input.is_a?(String)
|
58
|
+
[Email.new(email: input)]
|
59
|
+
elsif input.is_a?(::Mail::AddressContainer) && !input.instance_variable_get('@field').nil?
|
60
|
+
input.instance_variable_get('@field').addrs.map do |addr| # Mail::Address
|
61
|
+
Email.new(email: addr.address, name: addr.name)
|
89
62
|
end
|
63
|
+
elsif input.is_a?(::Mail::AddressContainer)
|
64
|
+
input.map do |addr|
|
65
|
+
Email.new(email: addr)
|
66
|
+
end
|
67
|
+
elsif input.is_a?(::Mail::StructuredField)
|
68
|
+
[Email.new(email: input.value)]
|
69
|
+
elsif input.nil?
|
70
|
+
[]
|
71
|
+
else
|
72
|
+
puts "unknown type #{input.class.name}"
|
90
73
|
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_personalizations(mail)
|
77
|
+
Personalization.new.tap do |p|
|
78
|
+
to_emails(mail.to).each { |to| p.add_to(to) }
|
79
|
+
to_emails(mail.cc).each { |cc| p.add_cc(cc) }
|
80
|
+
to_emails(mail.bcc).each { |bcc| p.add_bcc(bcc) }
|
81
|
+
p.add_substitution(Substitution.new(key: "%asm_group_unsubscribe_raw_url%", value: "<%asm_group_unsubscribe_raw_url%>"))
|
82
|
+
p.add_substitution(Substitution.new(key: "%asm_global_unsubscribe_raw_url%", value: "<%asm_global_unsubscribe_raw_url%>"))
|
83
|
+
p.add_substitution(Substitution.new(key: "%asm_preferences_raw_url%", value: "<%asm_preferences_raw_url%>"))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_attachment(part)
|
88
|
+
Attachment.new.tap do |a|
|
89
|
+
a.content = Base64.strict_encode64(part.body.decoded)
|
90
|
+
a.type = part.mime_type
|
91
|
+
a.filename = part.filename
|
91
92
|
|
92
|
-
|
93
|
+
disposition = get_disposition(part)
|
94
|
+
a.disposition = disposition unless disposition.nil?
|
95
|
+
|
96
|
+
has_content_id = part.header && part.has_content_id?
|
97
|
+
a.content_id = part.header['content_id'].value if has_content_id
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_disposition(message)
|
102
|
+
return if message.header.nil?
|
103
|
+
content_disp = message.header[:content_disposition]
|
104
|
+
return unless content_disp.respond_to?(:disposition_type)
|
105
|
+
content_disp.disposition_type
|
106
|
+
end
|
107
|
+
|
108
|
+
def add_content(sendgrid_mail, mail)
|
93
109
|
case mail.mime_type
|
94
110
|
when 'text/plain'
|
95
|
-
|
96
|
-
email.text = mail.body.decoded
|
111
|
+
sendgrid_mail.add_content(to_content(:plain, mail.body.decoded))
|
97
112
|
when 'text/html'
|
98
|
-
|
99
|
-
email.html = mail.body.decoded
|
113
|
+
sendgrid_mail.add_content(to_content(:html, mail.body.decoded))
|
100
114
|
when 'multipart/alternative', 'multipart/mixed', 'multipart/related'
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
mail.attachments.each do |
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
115
|
+
sendgrid_mail.add_content(to_content(:plain, mail.text_part.decoded)) if mail.text_part
|
116
|
+
sendgrid_mail.add_content(to_content(:html, mail.html_part.decoded)) if mail.html_part
|
117
|
+
|
118
|
+
mail.attachments.each do |part|
|
119
|
+
sendgrid_mail.add_attachment(to_attachment(part))
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def json_parse(text, symbolize=true)
|
125
|
+
JSON.parse(text.gsub(/:*\"*([\%a-zA-Z0-9_-]*)\"*(( *)=>\ *)/) { "\"#{$1}\":" }, symbolize_names: symbolize)
|
126
|
+
end
|
127
|
+
|
128
|
+
def add_send_options(sendgrid_mail, mail)
|
129
|
+
if mail['template_id']
|
130
|
+
sendgrid_mail.template_id = mail['template_id'].to_s
|
131
|
+
end
|
132
|
+
if mail['sections']
|
133
|
+
json_parse(mail['sections'].value, false).each do |key, value|
|
134
|
+
sendgrid_mail.add_section(Section.new(key: key, value: value))
|
135
|
+
end
|
136
|
+
end
|
137
|
+
if mail['headers']
|
138
|
+
json_parse(mail['headers'].value, false).each do |key, value|
|
139
|
+
sendgrid_mail.add_header(Header.new(key: key, value: value))
|
140
|
+
end
|
141
|
+
end
|
142
|
+
if mail['categories']
|
143
|
+
mail['categories'].value.split(",").each do |value|
|
144
|
+
sendgrid_mail.add_category(Category.new(name: value.strip))
|
145
|
+
end
|
146
|
+
end
|
147
|
+
if mail['custom_args']
|
148
|
+
json_parse(mail['custom_args'].value, false).each do |key, value|
|
149
|
+
sendgrid_mail.add_custom_arg(CustomArg.new(key: key, value: value))
|
150
|
+
end
|
151
|
+
end
|
152
|
+
if mail['send_at'] && mail['batch_id']
|
153
|
+
sendgrid_mail.send_at = mail['send_at'].value.to_i
|
154
|
+
sendgrid_mail.batch_id= mail['batch_id'].to_s
|
155
|
+
end
|
156
|
+
if mail['asm']
|
157
|
+
asm = json_parse(mail['asm'].value)
|
158
|
+
asm = asm.delete_if { |key, value| !key.to_s.match(/(group_id)|(groups_to_display)/) }
|
159
|
+
if asm[:group_id]
|
160
|
+
sendgrid_mail.asm = ASM.new(asm)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
if mail['ip_pool_name']
|
164
|
+
sendgrid_mail.ip_pool_name = mail['ip_pool_name'].to_s
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def add_mail_settings(sendgrid_mail, mail)
|
169
|
+
if mail['mail_settings']
|
170
|
+
settings = json_parse(mail['mail_settings'].value)
|
171
|
+
sendgrid_mail.mail_settings = MailSettings.new.tap do |m|
|
172
|
+
if settings[:bcc]
|
173
|
+
m.bcc = BccSettings.new(settings[:bcc])
|
174
|
+
end
|
175
|
+
if settings[:bypass_list_management]
|
176
|
+
m.bypass_list_management = BypassListManagement.new(settings[:bypass_list_management])
|
177
|
+
end
|
178
|
+
if settings[:footer]
|
179
|
+
m.footer = Footer.new(settings[:footer])
|
117
180
|
end
|
181
|
+
if settings[:sandbox_mode]
|
182
|
+
m.sandbox_mode = SandBoxMode.new(settings[:sandbox_mode])
|
183
|
+
end
|
184
|
+
if settings[:spam_check]
|
185
|
+
m.spam_check = SpamCheck.new(settings[:spam_check])
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
118
190
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
191
|
+
def add_tracking_settings(sendgrid_mail, mail)
|
192
|
+
if mail['tracking_settings']
|
193
|
+
settings = json_parse(mail['tracking_settings'].value)
|
194
|
+
sendgrid_mail.tracking_settings = TrackingSettings.new.tap do |t|
|
195
|
+
if settings[:click_tracking]
|
196
|
+
t.click_tracking = ClickTracking.new(settings[:click_tracking])
|
197
|
+
end
|
198
|
+
if settings[:open_tracking]
|
199
|
+
t.open_tracking = OpenTracking.new(settings[:open_tracking])
|
200
|
+
end
|
201
|
+
if settings[:subscription_tracking]
|
202
|
+
t.subscription_tracking = SubscriptionTracking.new(settings[:subscription_tracking])
|
203
|
+
end
|
204
|
+
if settings[:ganalytics]
|
205
|
+
t.ganalytics = Ganalytics.new(settings[:ganalytics])
|
123
206
|
end
|
124
207
|
end
|
125
208
|
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def perform_send_request(email)
|
212
|
+
result = client.mail._('send').post(request_body: email.to_json) # ლ(ಠ益ಠლ) that API
|
126
213
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
FileUtils.remove_entry_secure(dir)
|
214
|
+
if result.status_code && result.status_code.start_with?('4')
|
215
|
+
message = JSON.parse(result.body).fetch('errors').pop.fetch('message')
|
216
|
+
full_message = "Sendgrid delivery failed with #{result.status_code} #{message}"
|
217
|
+
|
218
|
+
settings[:raise_delivery_errors] ? raise(SendgridDeliveryError, full_message) : warn(full_message)
|
133
219
|
end
|
220
|
+
|
221
|
+
result
|
134
222
|
end
|
135
223
|
end
|
136
224
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
|
2
3
|
lib = File.expand_path('../lib', __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'sendgrid_actionmailer/version'
|
@@ -19,11 +20,11 @@ Gem::Specification.new do |spec|
|
|
19
20
|
spec.require_paths = ['lib']
|
20
21
|
|
21
22
|
spec.add_dependency 'mail', '~> 2.5'
|
22
|
-
spec.add_dependency 'sendgrid-ruby', '
|
23
|
+
spec.add_dependency 'sendgrid-ruby', '~> 5.0'
|
23
24
|
|
25
|
+
spec.add_development_dependency 'appraisal', '~> 2.1.0'
|
24
26
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
25
27
|
spec.add_development_dependency 'rake'
|
26
|
-
spec.add_development_dependency 'rspec', '~>3.2
|
27
|
-
spec.add_development_dependency '
|
28
|
-
spec.add_development_dependency 'webmock', '~> 1.24.6'
|
28
|
+
spec.add_development_dependency 'rspec', '~> 3.2'
|
29
|
+
spec.add_development_dependency 'webmock'
|
29
30
|
end
|