sendgrid-actionmailer 0.2.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|