sendgrid-ruby 1.1.6 → 6.6.2
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/.env_sample +1 -3
- data/.github/ISSUE_TEMPLATE/config.yml +10 -0
- data/.github/workflows/test-and-deploy.yml +120 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +5 -27
- data/.rubocop_todo.yml +127 -0
- data/CHANGELOG.md +417 -1
- data/CODE_OF_CONDUCT.md +73 -0
- data/CONTRIBUTING.md +202 -0
- data/Dockerfile +14 -0
- data/FIRST_TIMERS.md +79 -0
- data/Gemfile +4 -4
- data/ISSUE_TEMPLATE.md +30 -0
- data/LICENSE +21 -0
- data/Makefile +14 -0
- data/PULL_REQUEST_TEMPLATE.md +31 -0
- data/README.md +153 -229
- data/Rakefile +9 -3
- data/TROUBLESHOOTING.md +151 -0
- data/UPGRADE.md +5 -0
- data/USAGE.md +5144 -0
- data/config.ru +4 -0
- data/examples/accesssettings/accesssettings.rb +80 -0
- data/examples/alerts/alerts.rb +59 -0
- data/examples/apikeys/apikeys.rb +81 -0
- data/examples/asm/asm.rb +170 -0
- data/examples/browsers/browsers.rb +13 -0
- data/examples/campaigns/campaigns.rb +150 -0
- data/examples/categories/categories.rb +33 -0
- data/examples/clients/clients.rb +24 -0
- data/examples/contactdb/contactdb.rb +392 -0
- data/examples/devices/devices.rb +13 -0
- data/examples/emailactivity/emailactivity.rb +52 -0
- data/examples/geo/geo.rb +13 -0
- data/examples/helpers/eventwebhook/example.rb +16 -0
- data/examples/helpers/mail/example.rb +158 -0
- data/examples/helpers/settings/example.rb +23 -0
- data/examples/helpers/stats/example.rb +42 -0
- data/examples/ips/ips.rb +164 -0
- data/examples/mail/mail.rb +170 -0
- data/examples/mailboxproviders/mailboxproviders.rb +13 -0
- data/examples/mailsettings/mailsettings.rb +216 -0
- data/examples/partnersettings/partnersettings.rb +36 -0
- data/examples/scopes/scopes.rb +59 -0
- data/examples/senderauthentication/senderauthentication.rb +307 -0
- data/examples/senders/senders.rb +95 -0
- data/examples/stats/stats.rb +13 -0
- data/examples/subusers/subusers.rb +166 -0
- data/examples/suppression/suppression.rb +198 -0
- data/examples/templates/templates.rb +127 -0
- data/examples/trackingsettings/trackingsettings.rb +107 -0
- data/examples/user/user.rb +290 -0
- data/gemfiles/Sinatra_1.gemfile +6 -0
- data/gemfiles/Sinatra_2.gemfile +6 -0
- data/lib/rack/sendgrid_webhook_verification.rb +55 -0
- data/lib/sendgrid/base_interface.rb +40 -0
- data/lib/sendgrid/helpers/eventwebhook/eventwebhook.rb +50 -0
- data/lib/sendgrid/helpers/inbound/README.md +98 -0
- data/lib/sendgrid/helpers/inbound/app.rb +32 -0
- data/lib/sendgrid/helpers/inbound/config.yml +26 -0
- data/lib/sendgrid/helpers/inbound/public/index.html +10 -0
- data/lib/sendgrid/helpers/inbound/sample_data/default_data.txt +58 -0
- data/lib/sendgrid/helpers/inbound/sample_data/raw_data.txt +57 -0
- data/lib/sendgrid/helpers/inbound/sample_data/raw_data_with_attachments.txt +298 -0
- data/lib/sendgrid/helpers/inbound/send.rb +26 -0
- data/lib/sendgrid/helpers/ip_management/ip_management.rb +17 -0
- data/lib/sendgrid/helpers/mail/README.md +14 -0
- data/lib/sendgrid/helpers/mail/asm.rb +19 -0
- data/lib/sendgrid/helpers/mail/attachment.rb +55 -0
- data/lib/sendgrid/helpers/mail/bcc_settings.rb +19 -0
- data/lib/sendgrid/helpers/mail/bypass_list_management.rb +31 -0
- data/lib/sendgrid/helpers/mail/category.rb +18 -0
- data/lib/sendgrid/helpers/mail/click_tracking.rb +19 -0
- data/lib/sendgrid/helpers/mail/content.rb +19 -0
- data/lib/sendgrid/helpers/mail/custom_arg.rb +18 -0
- data/lib/sendgrid/helpers/mail/email.rb +32 -0
- data/lib/sendgrid/helpers/mail/footer.rb +21 -0
- data/lib/sendgrid/helpers/mail/ganalytics.rb +28 -0
- data/lib/sendgrid/helpers/mail/header.rb +18 -0
- data/lib/sendgrid/helpers/mail/mail.rb +122 -0
- data/lib/sendgrid/helpers/mail/mail_settings.rb +45 -0
- data/lib/sendgrid/helpers/mail/open_tracking.rb +19 -0
- data/lib/sendgrid/helpers/mail/personalization.rb +93 -0
- data/lib/sendgrid/helpers/mail/section.rb +18 -0
- data/lib/sendgrid/helpers/mail/spam_check.rb +21 -0
- data/lib/sendgrid/helpers/mail/subscription_tracking.rb +23 -0
- data/lib/sendgrid/helpers/mail/substitution.rb +18 -0
- data/lib/sendgrid/helpers/mail/tracking_settings.rb +39 -0
- data/lib/sendgrid/helpers/permissions/scope.rb +28 -0
- data/lib/sendgrid/helpers/permissions/scopes.yml +309 -0
- data/lib/sendgrid/helpers/settings/README.md +14 -0
- data/lib/sendgrid/helpers/settings/mail_settings_dto.rb +13 -0
- data/lib/sendgrid/helpers/settings/partner_settings_dto.rb +13 -0
- data/lib/sendgrid/helpers/settings/settings.rb +28 -0
- data/lib/sendgrid/helpers/settings/tracking_settings_dto.rb +22 -0
- data/lib/sendgrid/helpers/settings/user_settings_dto.rb +13 -0
- data/lib/sendgrid/helpers/stats/email_stats.rb +46 -0
- data/lib/sendgrid/helpers/stats/metrics.rb +33 -0
- data/lib/sendgrid/helpers/stats/stats_response.rb +29 -0
- data/lib/sendgrid/sendgrid.rb +21 -0
- data/lib/sendgrid/twilio_email.rb +21 -0
- data/lib/sendgrid/version.rb +1 -1
- data/lib/sendgrid-ruby.rb +32 -7
- data/mail_helper_v3.md +390 -0
- data/sendgrid-ruby.gemspec +16 -20
- data/spec/fixtures/event_webhook.rb +22 -0
- data/spec/rack/sendgrid_webhook_verification_spec.rb +142 -0
- data/spec/sendgrid/helpers/eventwebhook/eventwebhook_spec.rb +105 -0
- data/spec/sendgrid/helpers/ip_management/ip_management_spec.rb +12 -0
- data/spec/sendgrid/helpers/settings/mail_settings_dto_spec.rb +32 -0
- data/spec/sendgrid/helpers/settings/partner_settings_dto_spec.rb +24 -0
- data/spec/sendgrid/helpers/settings/settings_spec.rb +25 -0
- data/spec/sendgrid/helpers/settings/tracking_settings_dto_spec.rb +27 -0
- data/spec/sendgrid/helpers/settings/user_settings_dto_spec.rb +24 -0
- data/spec/sendgrid/helpers/stats/email_stats_spec.rb +111 -0
- data/spec/sendgrid/helpers/stats/metrics_spec.rb +45 -0
- data/spec/sendgrid/helpers/stats/stats_response_spec.rb +75 -0
- data/spec/sendgrid/sendgrid_spec.rb +11 -0
- data/spec/sendgrid/twilio_email_spec.rb +11 -0
- data/spec/spec_helper.rb +12 -1
- data/static/img/github-fork.png +0 -0
- data/static/img/github-sign-up.png +0 -0
- data/test/sendgrid/helpers/mail/test_attachment.rb +33 -0
- data/test/sendgrid/helpers/mail/test_category.rb +25 -0
- data/test/sendgrid/helpers/mail/test_email.rb +41 -0
- data/test/sendgrid/helpers/mail/test_mail.rb +260 -0
- data/test/sendgrid/helpers/mail/test_personalizations.rb +214 -0
- data/test/sendgrid/permissions/test_scopes.rb +36 -0
- data/test/sendgrid/test_sendgrid-ruby.rb +2729 -0
- data/twilio_sendgrid_logo.png +0 -0
- data/use-cases/README.md +17 -0
- data/use-cases/domain-authentication.md +5 -0
- data/use-cases/email-statistics.md +52 -0
- data/use-cases/legacy-templates.md +98 -0
- data/use-cases/personalizations.md +34 -0
- data/use-cases/sms.md +39 -0
- data/use-cases/transactional-templates.md +111 -0
- data/use-cases/twilio-email.md +13 -0
- data/use-cases/twilio-setup.md +54 -0
- metadata +187 -112
- data/.rspec +0 -2
- data/.travis.yml +0 -20
- data/FETCH_HEAD +0 -0
- data/Guardfile +0 -10
- data/LICENSE.txt +0 -22
- data/example.rb +0 -41
- data/lib/sendgrid/client.rb +0 -62
- data/lib/sendgrid/exceptions.rb +0 -7
- data/lib/sendgrid/mail.rb +0 -182
- data/lib/sendgrid/recipient.rb +0 -29
- data/lib/sendgrid/response.rb +0 -14
- data/lib/sendgrid/template.rb +0 -26
- data/lib/sendgrid/template_mailer.rb +0 -59
- data/spec/lib/sendgrid/client_spec.rb +0 -87
- data/spec/lib/sendgrid/mail_spec.rb +0 -151
- data/spec/lib/sendgrid/recipient_spec.rb +0 -91
- data/spec/lib/sendgrid/template_mailer_spec.rb +0 -86
- data/spec/lib/sendgrid/template_spec.rb +0 -61
- data/spec/lib/sendgrid_spec.rb +0 -7
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
require 'sendgrid-ruby'
|
|
2
|
+
|
|
3
|
+
sg = SendGrid::API.new(api_key: ENV['SENDGRID_API_KEY'])
|
|
4
|
+
|
|
5
|
+
##################################################
|
|
6
|
+
# Get a user's account information. #
|
|
7
|
+
# GET /user/account #
|
|
8
|
+
|
|
9
|
+
response = sg.client.user.account.get
|
|
10
|
+
puts response.status_code
|
|
11
|
+
puts response.body
|
|
12
|
+
puts response.headers
|
|
13
|
+
|
|
14
|
+
##################################################
|
|
15
|
+
# Retrieve your credit balance #
|
|
16
|
+
# GET /user/credits #
|
|
17
|
+
|
|
18
|
+
response = sg.client.user.credits.get
|
|
19
|
+
puts response.status_code
|
|
20
|
+
puts response.body
|
|
21
|
+
puts response.headers
|
|
22
|
+
|
|
23
|
+
##################################################
|
|
24
|
+
# Update your account email address #
|
|
25
|
+
# PUT /user/email #
|
|
26
|
+
|
|
27
|
+
data = JSON.parse('{
|
|
28
|
+
"email": "example@example.com"
|
|
29
|
+
}')
|
|
30
|
+
response = sg.client.user.email.put(request_body: data)
|
|
31
|
+
puts response.status_code
|
|
32
|
+
puts response.body
|
|
33
|
+
puts response.headers
|
|
34
|
+
|
|
35
|
+
##################################################
|
|
36
|
+
# Retrieve your account email address #
|
|
37
|
+
# GET /user/email #
|
|
38
|
+
|
|
39
|
+
response = sg.client.user.email.get
|
|
40
|
+
puts response.status_code
|
|
41
|
+
puts response.body
|
|
42
|
+
puts response.headers
|
|
43
|
+
|
|
44
|
+
##################################################
|
|
45
|
+
# Update your password #
|
|
46
|
+
# PUT /user/password #
|
|
47
|
+
|
|
48
|
+
data = JSON.parse('{
|
|
49
|
+
"new_password": "new_password",
|
|
50
|
+
"old_password": "old_password"
|
|
51
|
+
}')
|
|
52
|
+
response = sg.client.user.password.put(request_body: data)
|
|
53
|
+
puts response.status_code
|
|
54
|
+
puts response.body
|
|
55
|
+
puts response.headers
|
|
56
|
+
|
|
57
|
+
##################################################
|
|
58
|
+
# Update a user's profile #
|
|
59
|
+
# PATCH /user/profile #
|
|
60
|
+
|
|
61
|
+
data = JSON.parse('{
|
|
62
|
+
"city": "Orange",
|
|
63
|
+
"first_name": "Example",
|
|
64
|
+
"last_name": "User"
|
|
65
|
+
}')
|
|
66
|
+
response = sg.client.user.profile.patch(request_body: data)
|
|
67
|
+
puts response.status_code
|
|
68
|
+
puts response.body
|
|
69
|
+
puts response.headers
|
|
70
|
+
|
|
71
|
+
##################################################
|
|
72
|
+
# Get a user's profile #
|
|
73
|
+
# GET /user/profile #
|
|
74
|
+
|
|
75
|
+
response = sg.client.user.profile.get
|
|
76
|
+
puts response.status_code
|
|
77
|
+
puts response.body
|
|
78
|
+
puts response.headers
|
|
79
|
+
|
|
80
|
+
##################################################
|
|
81
|
+
# Cancel or pause a scheduled send #
|
|
82
|
+
# POST /user/scheduled_sends #
|
|
83
|
+
|
|
84
|
+
data = JSON.parse('{
|
|
85
|
+
"batch_id": "YOUR_BATCH_ID",
|
|
86
|
+
"status": "pause"
|
|
87
|
+
}')
|
|
88
|
+
response = sg.client.user.scheduled_sends.post(request_body: data)
|
|
89
|
+
puts response.status_code
|
|
90
|
+
puts response.body
|
|
91
|
+
puts response.headers
|
|
92
|
+
|
|
93
|
+
##################################################
|
|
94
|
+
# Retrieve all scheduled sends #
|
|
95
|
+
# GET /user/scheduled_sends #
|
|
96
|
+
|
|
97
|
+
response = sg.client.user.scheduled_sends.get
|
|
98
|
+
puts response.status_code
|
|
99
|
+
puts response.body
|
|
100
|
+
puts response.headers
|
|
101
|
+
|
|
102
|
+
##################################################
|
|
103
|
+
# Update user scheduled send information #
|
|
104
|
+
# PATCH /user/scheduled_sends/{batch_id} #
|
|
105
|
+
|
|
106
|
+
data = JSON.parse('{
|
|
107
|
+
"status": "pause"
|
|
108
|
+
}')
|
|
109
|
+
batch_id = 'test_url_param'
|
|
110
|
+
response = sg.client.user.scheduled_sends._(batch_id).patch(request_body: data)
|
|
111
|
+
puts response.status_code
|
|
112
|
+
puts response.body
|
|
113
|
+
puts response.headers
|
|
114
|
+
|
|
115
|
+
##################################################
|
|
116
|
+
# Retrieve scheduled send #
|
|
117
|
+
# GET /user/scheduled_sends/{batch_id} #
|
|
118
|
+
|
|
119
|
+
batch_id = 'test_url_param'
|
|
120
|
+
response = sg.client.user.scheduled_sends._(batch_id).get
|
|
121
|
+
puts response.status_code
|
|
122
|
+
puts response.body
|
|
123
|
+
puts response.headers
|
|
124
|
+
|
|
125
|
+
##################################################
|
|
126
|
+
# Delete a cancellation or pause of a scheduled send #
|
|
127
|
+
# DELETE /user/scheduled_sends/{batch_id} #
|
|
128
|
+
|
|
129
|
+
batch_id = 'test_url_param'
|
|
130
|
+
response = sg.client.user.scheduled_sends._(batch_id).delete
|
|
131
|
+
puts response.status_code
|
|
132
|
+
puts response.body
|
|
133
|
+
puts response.headers
|
|
134
|
+
|
|
135
|
+
##################################################
|
|
136
|
+
# Update Enforced TLS settings #
|
|
137
|
+
# PATCH /user/settings/enforced_tls #
|
|
138
|
+
|
|
139
|
+
data = JSON.parse('{
|
|
140
|
+
"require_tls": true,
|
|
141
|
+
"require_valid_cert": false
|
|
142
|
+
}')
|
|
143
|
+
response = sg.client.user.settings.enforced_tls.patch(request_body: data)
|
|
144
|
+
puts response.status_code
|
|
145
|
+
puts response.body
|
|
146
|
+
puts response.headers
|
|
147
|
+
|
|
148
|
+
##################################################
|
|
149
|
+
# Retrieve current Enforced TLS settings. #
|
|
150
|
+
# GET /user/settings/enforced_tls #
|
|
151
|
+
|
|
152
|
+
response = sg.client.user.settings.enforced_tls.get
|
|
153
|
+
puts response.status_code
|
|
154
|
+
puts response.body
|
|
155
|
+
puts response.headers
|
|
156
|
+
|
|
157
|
+
##################################################
|
|
158
|
+
# Update your username #
|
|
159
|
+
# PUT /user/username #
|
|
160
|
+
|
|
161
|
+
data = JSON.parse('{
|
|
162
|
+
"username": "test_username"
|
|
163
|
+
}')
|
|
164
|
+
response = sg.client.user.username.put(request_body: data)
|
|
165
|
+
puts response.status_code
|
|
166
|
+
puts response.body
|
|
167
|
+
puts response.headers
|
|
168
|
+
|
|
169
|
+
##################################################
|
|
170
|
+
# Retrieve your username #
|
|
171
|
+
# GET /user/username #
|
|
172
|
+
|
|
173
|
+
response = sg.client.user.username.get
|
|
174
|
+
puts response.status_code
|
|
175
|
+
puts response.body
|
|
176
|
+
puts response.headers
|
|
177
|
+
|
|
178
|
+
##################################################
|
|
179
|
+
# Update Event Notification Settings #
|
|
180
|
+
# PATCH /user/webhooks/event/settings #
|
|
181
|
+
|
|
182
|
+
data = JSON.parse('{
|
|
183
|
+
"bounce": true,
|
|
184
|
+
"click": true,
|
|
185
|
+
"deferred": true,
|
|
186
|
+
"delivered": true,
|
|
187
|
+
"dropped": true,
|
|
188
|
+
"enabled": true,
|
|
189
|
+
"group_resubscribe": true,
|
|
190
|
+
"group_unsubscribe": true,
|
|
191
|
+
"open": true,
|
|
192
|
+
"processed": true,
|
|
193
|
+
"spam_report": true,
|
|
194
|
+
"unsubscribe": true,
|
|
195
|
+
"url": "url"
|
|
196
|
+
}')
|
|
197
|
+
response = sg.client.user.webhooks.event.settings.patch(request_body: data)
|
|
198
|
+
puts response.status_code
|
|
199
|
+
puts response.body
|
|
200
|
+
puts response.headers
|
|
201
|
+
|
|
202
|
+
##################################################
|
|
203
|
+
# Retrieve Event Webhook settings #
|
|
204
|
+
# GET /user/webhooks/event/settings #
|
|
205
|
+
|
|
206
|
+
response = sg.client.user.webhooks.event.settings.get
|
|
207
|
+
puts response.status_code
|
|
208
|
+
puts response.body
|
|
209
|
+
puts response.headers
|
|
210
|
+
|
|
211
|
+
##################################################
|
|
212
|
+
# Test Event Notification Settings #
|
|
213
|
+
# POST /user/webhooks/event/test #
|
|
214
|
+
|
|
215
|
+
data = JSON.parse('{
|
|
216
|
+
"url": "url"
|
|
217
|
+
}')
|
|
218
|
+
response = sg.client.user.webhooks.event.test.post(request_body: data)
|
|
219
|
+
puts response.status_code
|
|
220
|
+
puts response.body
|
|
221
|
+
puts response.headers
|
|
222
|
+
|
|
223
|
+
##################################################
|
|
224
|
+
# Create a parse setting #
|
|
225
|
+
# POST /user/webhooks/parse/settings #
|
|
226
|
+
|
|
227
|
+
data = JSON.parse('{
|
|
228
|
+
"hostname": "myhostname.com",
|
|
229
|
+
"send_raw": false,
|
|
230
|
+
"spam_check": true,
|
|
231
|
+
"url": "http://email.myhosthame.com"
|
|
232
|
+
}')
|
|
233
|
+
response = sg.client.user.webhooks.parse.settings.post(request_body: data)
|
|
234
|
+
puts response.status_code
|
|
235
|
+
puts response.body
|
|
236
|
+
puts response.headers
|
|
237
|
+
|
|
238
|
+
##################################################
|
|
239
|
+
# Retrieve all parse settings #
|
|
240
|
+
# GET /user/webhooks/parse/settings #
|
|
241
|
+
|
|
242
|
+
response = sg.client.user.webhooks.parse.settings.get
|
|
243
|
+
puts response.status_code
|
|
244
|
+
puts response.body
|
|
245
|
+
puts response.headers
|
|
246
|
+
|
|
247
|
+
##################################################
|
|
248
|
+
# Update a parse setting #
|
|
249
|
+
# PATCH /user/webhooks/parse/settings/{hostname} #
|
|
250
|
+
|
|
251
|
+
data = JSON.parse('{
|
|
252
|
+
"send_raw": true,
|
|
253
|
+
"spam_check": false,
|
|
254
|
+
"url": "http://newdomain.com/parse"
|
|
255
|
+
}')
|
|
256
|
+
hostname = 'test_url_param'
|
|
257
|
+
response = sg.client.user.webhooks.parse.settings._(hostname).patch(request_body: data)
|
|
258
|
+
puts response.status_code
|
|
259
|
+
puts response.body
|
|
260
|
+
puts response.headers
|
|
261
|
+
|
|
262
|
+
##################################################
|
|
263
|
+
# Retrieve a specific parse setting #
|
|
264
|
+
# GET /user/webhooks/parse/settings/{hostname} #
|
|
265
|
+
|
|
266
|
+
hostname = 'test_url_param'
|
|
267
|
+
response = sg.client.user.webhooks.parse.settings._(hostname).get
|
|
268
|
+
puts response.status_code
|
|
269
|
+
puts response.body
|
|
270
|
+
puts response.headers
|
|
271
|
+
|
|
272
|
+
##################################################
|
|
273
|
+
# Delete a parse setting #
|
|
274
|
+
# DELETE /user/webhooks/parse/settings/{hostname} #
|
|
275
|
+
|
|
276
|
+
hostname = 'test_url_param'
|
|
277
|
+
response = sg.client.user.webhooks.parse.settings._(hostname).delete
|
|
278
|
+
puts response.status_code
|
|
279
|
+
puts response.body
|
|
280
|
+
puts response.headers
|
|
281
|
+
|
|
282
|
+
##################################################
|
|
283
|
+
# Retrieves Inbound Parse Webhook statistics. #
|
|
284
|
+
# GET /user/webhooks/parse/stats #
|
|
285
|
+
|
|
286
|
+
params = JSON.parse('{"aggregated_by": "day", "limit": "test_string", "start_date": "2016-01-01", "end_date": "2016-04-01", "offset": "test_string"}')
|
|
287
|
+
response = sg.client.user.webhooks.parse.stats.get(query_params: params)
|
|
288
|
+
puts response.status_code
|
|
289
|
+
puts response.body
|
|
290
|
+
puts response.headers
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rack
|
|
4
|
+
# Middleware that verifies webhooks from SendGrid using the EventWebhook
|
|
5
|
+
# verifier.
|
|
6
|
+
#
|
|
7
|
+
# The middleware takes a public key with which to set up the request
|
|
8
|
+
# validator and any number of paths. When a path matches the incoming request
|
|
9
|
+
# path, the request will be verified using the signature and timestamp of the
|
|
10
|
+
# request.
|
|
11
|
+
#
|
|
12
|
+
# Example:
|
|
13
|
+
#
|
|
14
|
+
# require 'rack'
|
|
15
|
+
# use Rack::SendGridWebhookVerification, ENV['PUBLIC_KEY'], /\/emails/
|
|
16
|
+
#
|
|
17
|
+
# The above appends this middleware to the stack, using a public key saved in
|
|
18
|
+
# the ENV and only against paths that match /\/emails/. If the request
|
|
19
|
+
# validates then it gets passed on to the action as normal. If the request
|
|
20
|
+
# doesn't validate then the middleware responds immediately with a 403 status.
|
|
21
|
+
class SendGridWebhookVerification
|
|
22
|
+
def initialize(app, public_key, *paths)
|
|
23
|
+
@app = app
|
|
24
|
+
@public_key = public_key
|
|
25
|
+
@path_regex = Regexp.union(paths)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def call(env)
|
|
29
|
+
return @app.call(env) unless env['PATH_INFO'].match(@path_regex)
|
|
30
|
+
|
|
31
|
+
request = Rack::Request.new(env)
|
|
32
|
+
|
|
33
|
+
event_webhook = SendGrid::EventWebhook.new
|
|
34
|
+
ec_public_key = event_webhook.convert_public_key_to_ecdsa(@public_key)
|
|
35
|
+
verified = event_webhook.verify_signature(
|
|
36
|
+
ec_public_key,
|
|
37
|
+
request.body.read,
|
|
38
|
+
request.env[SendGrid::EventWebhookHeader::SIGNATURE],
|
|
39
|
+
request.env[SendGrid::EventWebhookHeader::TIMESTAMP]
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
request.body.rewind
|
|
43
|
+
|
|
44
|
+
if verified
|
|
45
|
+
@app.call(env)
|
|
46
|
+
else
|
|
47
|
+
[
|
|
48
|
+
403,
|
|
49
|
+
{ 'Content-Type' => 'text/plain' },
|
|
50
|
+
['SendGrid Request Verification Failed.']
|
|
51
|
+
]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'ruby_http_client'
|
|
2
|
+
require_relative 'version'
|
|
3
|
+
|
|
4
|
+
# Initialize the HTTP client
|
|
5
|
+
class BaseInterface
|
|
6
|
+
attr_accessor :client
|
|
7
|
+
attr_reader :request_headers, :host, :version, :impersonate_subuser, :http_options
|
|
8
|
+
|
|
9
|
+
# * *Args* :
|
|
10
|
+
# - +auth+ -> authorization header value
|
|
11
|
+
# - +host+ -> the base URL for the API
|
|
12
|
+
# - +request_headers+ -> any headers that you want to be globally applied
|
|
13
|
+
# - +version+ -> the version of the API you wish to access,
|
|
14
|
+
# currently only "v3" is supported
|
|
15
|
+
# - +impersonate_subuser+ -> the subuser to impersonate, will be passed
|
|
16
|
+
# in the "On-Behalf-Of" header
|
|
17
|
+
# - +http_options+ -> http options that you want to be globally applied to each request
|
|
18
|
+
#
|
|
19
|
+
def initialize(auth:, host:, request_headers: nil, version: nil, impersonate_subuser: nil, http_options: {})
|
|
20
|
+
@auth = auth
|
|
21
|
+
@host = host
|
|
22
|
+
@version = version || 'v3'
|
|
23
|
+
@impersonate_subuser = impersonate_subuser
|
|
24
|
+
@user_agent = "sendgrid/#{SendGrid::VERSION};ruby"
|
|
25
|
+
@request_headers = JSON.parse('
|
|
26
|
+
{
|
|
27
|
+
"Authorization": "' + @auth + '",
|
|
28
|
+
"Accept": "application/json",
|
|
29
|
+
"User-Agent": "' + @user_agent + '"
|
|
30
|
+
}
|
|
31
|
+
')
|
|
32
|
+
@request_headers['On-Behalf-Of'] = @impersonate_subuser if @impersonate_subuser
|
|
33
|
+
|
|
34
|
+
@request_headers = @request_headers.merge(request_headers) if request_headers
|
|
35
|
+
@http_options = http_options
|
|
36
|
+
@client = SendGrid::Client.new(host: "#{@host}/#{@version}",
|
|
37
|
+
request_headers: @request_headers,
|
|
38
|
+
http_options: @http_options)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
require 'digest'
|
|
3
|
+
require 'openssl'
|
|
4
|
+
|
|
5
|
+
module SendGrid
|
|
6
|
+
# This class allows you to use the Event Webhook feature. Read the docs for
|
|
7
|
+
# more details: https://sendgrid.com/docs/for-developers/tracking-events/event
|
|
8
|
+
class EventWebhook
|
|
9
|
+
# * *Args* :
|
|
10
|
+
# - +public_key+ -> verification key under Mail Settings
|
|
11
|
+
#
|
|
12
|
+
def convert_public_key_to_ecdsa(public_key)
|
|
13
|
+
verify_engine
|
|
14
|
+
OpenSSL::PKey::EC.new(Base64.decode64(public_key))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# * *Args* :
|
|
18
|
+
# - +public_key+ -> elliptic curve public key
|
|
19
|
+
# - +payload+ -> event payload in the request body
|
|
20
|
+
# - +signature+ -> signature value obtained from the 'X-Twilio-Email-Event-Webhook-Signature' header
|
|
21
|
+
# - +timestamp+ -> timestamp value obtained from the 'X-Twilio-Email-Event-Webhook-Timestamp' header
|
|
22
|
+
def verify_signature(public_key, payload, signature, timestamp)
|
|
23
|
+
verify_engine
|
|
24
|
+
timestamped_playload = "#{timestamp}#{payload}"
|
|
25
|
+
payload_digest = Digest::SHA256.digest(timestamped_playload)
|
|
26
|
+
decoded_signature = Base64.decode64(signature)
|
|
27
|
+
public_key.dsa_verify_asn1(payload_digest, decoded_signature)
|
|
28
|
+
rescue StandardError
|
|
29
|
+
false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def verify_engine
|
|
33
|
+
# JRuby does not fully support ECDSA: https://github.com/jruby/jruby-openssl/issues/193
|
|
34
|
+
raise NotSupportedError, "Event Webhook verification is not supported by JRuby" if RUBY_PLATFORM == "java"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class Error < ::RuntimeError
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class NotSupportedError < Error
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# This class lists headers that get posted to the webhook. Read the docs for
|
|
45
|
+
# more details: https://sendgrid.com/docs/for-developers/tracking-events/event
|
|
46
|
+
class EventWebhookHeader
|
|
47
|
+
SIGNATURE = "HTTP_X_TWILIO_EMAIL_EVENT_WEBHOOK_SIGNATURE".freeze
|
|
48
|
+
TIMESTAMP = "HTTP_X_TWILIO_EMAIL_EVENT_WEBHOOK_TIMESTAMP".freeze
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
**This helper is a stand alone module to help get you started consuming and processing Inbound Parse data.**
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Installation](#installation)
|
|
6
|
+
- [Quick Start for Local Testing with Sample Data](#quick-start-for-local-testing-with-sample-data)
|
|
7
|
+
- [Quick Start for Local Testing with Real Data](#quick-start-for-local-testing-with-real-data)
|
|
8
|
+
- [Code Walkthrough](#code-walkthrough)
|
|
9
|
+
- [app.rb](#apprb)
|
|
10
|
+
- [config.yml](#configyml)
|
|
11
|
+
- [send.rb & /sample_data](#sendrb--sampledata)
|
|
12
|
+
- [Contributing](#contributing)
|
|
13
|
+
|
|
14
|
+
<a name="quick_start_local_sample"></a>
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Installation
|
|
18
|
+
|
|
19
|
+
In addition to the installation instructions in
|
|
20
|
+
[the main readme](../../../../README.md#installation),
|
|
21
|
+
you must also add sinatra to your Gemfile:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
gem 'sinatra', '>= 1.4.7', '< 3'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Quick Start for Local Testing with Sample Data
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone https://github.com/sendgrid/sendgrid-ruby.git
|
|
32
|
+
cd sendgrid-ruby
|
|
33
|
+
bundle install
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Run the Inbound Parse listener in your terminal:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
rackup
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
In another terminal, run the test data sender:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
cd [path to sendgrid-ruby]
|
|
46
|
+
bundle install
|
|
47
|
+
ruby ./lib/sendgrid/helpers/inbound/send.rb ./lib/sendgrid/helpers/inbound/sample_data/default_data.txt
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
More sample data can be found [here](sample_data).
|
|
51
|
+
|
|
52
|
+
View the results in the first terminal.
|
|
53
|
+
|
|
54
|
+
<a name="quick_start_local_real"></a>
|
|
55
|
+
# Quick Start for Local Testing with Real Data
|
|
56
|
+
|
|
57
|
+
[Setup your MX records.](https://sendgrid.com/docs/Classroom/Basics/Inbound_Parse_Webhook/setting_up_the_inbound_parse_webhook.html#-Setup) Depending on your domain name host, you may need to wait up to 48 hours for the settings to propagate.
|
|
58
|
+
|
|
59
|
+
Run the Inbound Parse listener in your terminal:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git clone https://github.com/sendgrid/sendgrid-ruby.git
|
|
63
|
+
cd sendgrid-ruby
|
|
64
|
+
bundle install
|
|
65
|
+
rackup
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
In another terminal, use [ngrok](https://ngrok.com/) to allow external access to your machine:
|
|
69
|
+
```bash
|
|
70
|
+
ngrok http 9292
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Update your Twilio SendGrid Incoming Parse settings: [Settings Page](https://app.sendgrid.com/settings/parse) | [Docs](https://sendgrid.com/docs/Classroom/Basics/Inbound_Parse_Webhook/setting_up_the_inbound_parse_webhook.html#-Pointing-to-a-Hostname-and-URL)
|
|
74
|
+
|
|
75
|
+
- For the HOSTNAME field, use the domain that you changed the MX records (e.g. inbound.yourdomain.com)
|
|
76
|
+
- For the URL field, use the URL generated by ngrok + /inbound, e.g http://XXXXXXX.ngrok.io/inbound
|
|
77
|
+
|
|
78
|
+
Next, send an email to [anything]@inbound.yourdomain.com, then look at the terminal where you started the Inbound Parse listener.
|
|
79
|
+
|
|
80
|
+
<a name="code_walkthrough"></a>
|
|
81
|
+
# Code Walkthrough
|
|
82
|
+
|
|
83
|
+
## app.rb
|
|
84
|
+
|
|
85
|
+
This module runs a [Sinatra](http://www.sinatrarb.com/) server, that by default (you can change those settings [here](config.yml)), listens for POSTs on http://localhost:9292. When the server receives the POST, it parses and prints the key/value data.
|
|
86
|
+
|
|
87
|
+
## config.yml
|
|
88
|
+
|
|
89
|
+
This module loads application environment variables (located in [config.yml](config.yml)).
|
|
90
|
+
|
|
91
|
+
## send.rb & /sample_data
|
|
92
|
+
|
|
93
|
+
This module is used to send sample test data. It is useful for testing and development, particularly while you wait for your MX records to propagate.
|
|
94
|
+
|
|
95
|
+
<a name="contributing"></a>
|
|
96
|
+
# Contributing
|
|
97
|
+
|
|
98
|
+
If you would like to contribute to this project, please see our [contributing guide](../../../../CONTRIBUTING.md). Thanks!
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require 'sinatra'
|
|
3
|
+
rescue LoadError
|
|
4
|
+
puts <<-NOTE
|
|
5
|
+
As of sengrid verison 6, sinatra is no longer specified as a dependency of
|
|
6
|
+
the sendgrid gem. All the functionality of the inbound server is still the same
|
|
7
|
+
and fully supported, but you just need to include the sinatra dependency in your gemfile
|
|
8
|
+
yourself, like so:
|
|
9
|
+
|
|
10
|
+
gem 'sinatra', '>= 1.4.7', '< 3'
|
|
11
|
+
NOTE
|
|
12
|
+
raise
|
|
13
|
+
end
|
|
14
|
+
require 'logger'
|
|
15
|
+
require 'json'
|
|
16
|
+
require 'yaml'
|
|
17
|
+
|
|
18
|
+
class Main < Sinatra::Base
|
|
19
|
+
configure :production, :development do
|
|
20
|
+
enable :logging
|
|
21
|
+
set :config, YAML.load_file("#{File.dirname(__FILE__)}/config.yml")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
get '/' do
|
|
25
|
+
redirect to('index.html')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
post settings.config['endpoint'] do
|
|
29
|
+
filtered = params.select { |k, _v| settings.config['keys'].include?(k) }
|
|
30
|
+
logger.info JSON.pretty_generate(filtered)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Incoming Parse endpoint for receiver
|
|
2
|
+
endpoint: '/inbound'
|
|
3
|
+
|
|
4
|
+
# List all Incoming Parse fields you would like parsed
|
|
5
|
+
# Reference: https://sendgrid.com/docs/Classroom/Basics/Inbound_Parse_Webhook/setting_up_the_inbound_parse_webhook.html
|
|
6
|
+
keys:
|
|
7
|
+
- from
|
|
8
|
+
- attachments
|
|
9
|
+
- headers
|
|
10
|
+
- text
|
|
11
|
+
- envelope
|
|
12
|
+
- to
|
|
13
|
+
- html
|
|
14
|
+
- sender_ip
|
|
15
|
+
- attachment-info
|
|
16
|
+
- subject
|
|
17
|
+
- dkim
|
|
18
|
+
- SPF
|
|
19
|
+
- charsets
|
|
20
|
+
- content-ids
|
|
21
|
+
- spam_report
|
|
22
|
+
- spam_score
|
|
23
|
+
- email
|
|
24
|
+
|
|
25
|
+
# URL that the sender will POST to
|
|
26
|
+
host: 'http://127.0.0.1:9292/inbound'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<html>
|
|
2
|
+
<head>
|
|
3
|
+
<title>Twilio SendGrid Incoming Parse</title>
|
|
4
|
+
</head>
|
|
5
|
+
<body>
|
|
6
|
+
<h1>You have successfuly launched the server!</h1>
|
|
7
|
+
|
|
8
|
+
Check out <a href="https://github.com/sendgrid/sendgrid-ruby/tree/HEAD/sendgrid/helpers/inbound">the documentation</a> on how to use this software to utilize the SendGrid Inbound Parse webhook.
|
|
9
|
+
</body>
|
|
10
|
+
</html>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
--xYzZY
|
|
2
|
+
Content-Disposition: form-data; name="headers"
|
|
3
|
+
|
|
4
|
+
MIME-Version: 1.0
|
|
5
|
+
Received: by 0.0.0.0 with HTTP; Wed, 10 Aug 2016 18:10:13 -0700 (PDT)
|
|
6
|
+
From: Example User <test@example.com>
|
|
7
|
+
Date: Wed, 10 Aug 2016 18:10:13 -0700
|
|
8
|
+
Subject: Inbound Parse Test Data
|
|
9
|
+
To: inbound@inbound.example.com
|
|
10
|
+
Content-Type: multipart/alternative; boundary=001a113df448cad2d00539c16e89
|
|
11
|
+
|
|
12
|
+
--xYzZY
|
|
13
|
+
Content-Disposition: form-data; name="dkim"
|
|
14
|
+
|
|
15
|
+
{@sendgrid.com : pass}
|
|
16
|
+
--xYzZY
|
|
17
|
+
Content-Disposition: form-data; name="to"
|
|
18
|
+
|
|
19
|
+
inbound@inbound.example.com
|
|
20
|
+
--xYzZY
|
|
21
|
+
Content-Disposition: form-data; name="html"
|
|
22
|
+
|
|
23
|
+
<html><body><strong>Hello Twilio SendGrid!</body></html>
|
|
24
|
+
|
|
25
|
+
--xYzZY
|
|
26
|
+
Content-Disposition: form-data; name="from"
|
|
27
|
+
|
|
28
|
+
Example User <test@example.com>
|
|
29
|
+
--xYzZY
|
|
30
|
+
Content-Disposition: form-data; name="text"
|
|
31
|
+
|
|
32
|
+
Hello Twilio SendGrid!
|
|
33
|
+
|
|
34
|
+
--xYzZY
|
|
35
|
+
Content-Disposition: form-data; name="sender_ip"
|
|
36
|
+
|
|
37
|
+
0.0.0.0
|
|
38
|
+
--xYzZY
|
|
39
|
+
Content-Disposition: form-data; name="envelope"
|
|
40
|
+
|
|
41
|
+
{"to":["inbound@inbound.example.com"],"from":"test@example.com"}
|
|
42
|
+
--xYzZY
|
|
43
|
+
Content-Disposition: form-data; name="attachments"
|
|
44
|
+
|
|
45
|
+
0
|
|
46
|
+
--xYzZY
|
|
47
|
+
Content-Disposition: form-data; name="subject"
|
|
48
|
+
|
|
49
|
+
Testing non-raw
|
|
50
|
+
--xYzZY
|
|
51
|
+
Content-Disposition: form-data; name="charsets"
|
|
52
|
+
|
|
53
|
+
{"to":"UTF-8","html":"UTF-8","subject":"UTF-8","from":"UTF-8","text":"UTF-8"}
|
|
54
|
+
--xYzZY
|
|
55
|
+
Content-Disposition: form-data; name="SPF"
|
|
56
|
+
|
|
57
|
+
pass
|
|
58
|
+
--xYzZY--
|