sidemail 0.1.1
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 +7 -0
- data/README.md +395 -0
- data/lib/sidemail/client.rb +126 -0
- data/lib/sidemail/error.rb +14 -0
- data/lib/sidemail/paginated_response.rb +92 -0
- data/lib/sidemail/resource.rb +55 -0
- data/lib/sidemail/resources/contact.rb +37 -0
- data/lib/sidemail/resources/domain.rb +26 -0
- data/lib/sidemail/resources/email.rb +31 -0
- data/lib/sidemail/resources/messenger.rb +36 -0
- data/lib/sidemail/resources/project.rb +31 -0
- data/lib/sidemail/version.rb +5 -0
- data/lib/sidemail.rb +22 -0
- metadata +126 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 10bbf092935efd7fce7c872480e85a75d50193f2c40d167c3c091b6f2e476061
|
|
4
|
+
data.tar.gz: 5ec58344812bf3582f84426ecaeb009a47019bed904a08f635b33886fcbee513
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4366cbc8907914d820ec5b2e9f80dcac57a409109c92861efa479921b4c015467e6c73bfac1b76f80a05ddfbd8e967794f4f4aee8e479c325bcaed22676a6b5e
|
|
7
|
+
data.tar.gz: 1f0c2492beadd451a6971f3b82e9d1c87208082f5908ef926d0cbe2d75b22a7a4a9a85910c946f51e5981f469ae5d438597900aae660ed964db54c10aa28a7b2
|
data/README.md
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
# Sidemail Ruby Library
|
|
2
|
+
|
|
3
|
+
Official Sidemail.io Ruby library providing convenient access to the Sidemail API from Ruby applications.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Ruby 2.6+
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Using RubyGems:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
gem install sidemail
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or using Bundler:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
bundle add sidemail
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
First, the client needs to be configured with your project's API key, which you can find in the Sidemail Dashboard after you sign up.
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
require "sidemail"
|
|
29
|
+
|
|
30
|
+
# Initialize with API key
|
|
31
|
+
sm = Sidemail.new(api_key: "your-api-key")
|
|
32
|
+
|
|
33
|
+
# Or set environment variable SIDEMAIL_API_KEY
|
|
34
|
+
# sm = Sidemail.new
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
response = sm.send_email(
|
|
39
|
+
toAddress: "user@email.com",
|
|
40
|
+
fromAddress: "you@example.com",
|
|
41
|
+
fromName: "Your App",
|
|
42
|
+
templateName: "Welcome",
|
|
43
|
+
templateProps: { foo: "bar" }
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
puts "Email sent! ID: #{response.id}"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The response looks like:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"id": "5e858953daf20f3aac50a3da",
|
|
54
|
+
"status": "queued"
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Shortcut `sm.send_email(...)` calls `sm.email.send(...)` under the hood.
|
|
59
|
+
|
|
60
|
+
### Authentication
|
|
61
|
+
|
|
62
|
+
Explicit key:
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
require "sidemail"
|
|
66
|
+
sm = Sidemail.new(api_key: "your-api-key")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Or if you set environment variable `SIDEMAIL_API_KEY`, then simply:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
require "sidemail"
|
|
73
|
+
sm = Sidemail.new # reads SIDEMAIL_API_KEY
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Client configuration
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
require "sidemail"
|
|
80
|
+
|
|
81
|
+
sm = Sidemail.new(
|
|
82
|
+
api_key: "your-api-key",
|
|
83
|
+
base_url: "https://api.sidemail.io/v1", # override for testing/mocking
|
|
84
|
+
timeout: 10.0, # per-request timeout (seconds)
|
|
85
|
+
http_client: custom_http_client, # custom Net::HTTP client (proxies, retries, etc.)
|
|
86
|
+
)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Email Sending Examples
|
|
90
|
+
|
|
91
|
+
### Password reset template
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
sm.send_email(
|
|
95
|
+
toAddress: "user@email.com",
|
|
96
|
+
fromAddress: "you@example.com",
|
|
97
|
+
fromName: "Your App",
|
|
98
|
+
templateName: "Password reset",
|
|
99
|
+
templateProps: { resetUrl: "https://your.app/reset?token=123" }
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Schedule email delivery
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
require "date"
|
|
107
|
+
|
|
108
|
+
# Schedule for 60 minutes from now
|
|
109
|
+
scheduled_iso = (Time.now + 60 * 60).utc.iso8601
|
|
110
|
+
|
|
111
|
+
sm.send_email(
|
|
112
|
+
toAddress: "user@email.com",
|
|
113
|
+
fromAddress: "your@startup.com",
|
|
114
|
+
fromName: "Startup name",
|
|
115
|
+
templateName: "Welcome",
|
|
116
|
+
templateProps: { firstName: "Patrik" },
|
|
117
|
+
scheduledAt: scheduled_iso
|
|
118
|
+
)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Template with dynamic list
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
sm.send_email(
|
|
125
|
+
toAddress: "user@email.com",
|
|
126
|
+
fromAddress: "your@startup.com",
|
|
127
|
+
fromName: "Startup name",
|
|
128
|
+
templateName: "Template with dynamic list",
|
|
129
|
+
templateProps: {
|
|
130
|
+
list: [
|
|
131
|
+
{ text: "Dynamic list" },
|
|
132
|
+
{ text: "allows you to generate email template content" },
|
|
133
|
+
{ text: "based on template props." }
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Custom HTML email
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
sm.send_email(
|
|
143
|
+
toAddress: "user@email.com",
|
|
144
|
+
fromAddress: "your@startup.com",
|
|
145
|
+
fromName: "Startup name",
|
|
146
|
+
subject: "Testing html only custom emails :)",
|
|
147
|
+
html: "<html><body><h1>Hello world! 👋</h1></body></html>"
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Custom plain text email
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
sm.send_email(
|
|
155
|
+
toAddress: "user@email.com",
|
|
156
|
+
fromAddress: "your@startup.com",
|
|
157
|
+
fromName: "Startup name",
|
|
158
|
+
subject: "Testing plain-text only custom emails :)",
|
|
159
|
+
text: "Hello world! 👋"
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Error handling
|
|
164
|
+
|
|
165
|
+
The SDK raises `Sidemail::Error` for all errors. API errors include `message`, `http_status`, `error_code`, and `more_info`.
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
require "sidemail"
|
|
169
|
+
|
|
170
|
+
sm = Sidemail.new(api_key: "your-api-key")
|
|
171
|
+
|
|
172
|
+
begin
|
|
173
|
+
sm.send_email(
|
|
174
|
+
toAddress: "user@example.com",
|
|
175
|
+
fromAddress: "you@example.com",
|
|
176
|
+
subject: "Hello",
|
|
177
|
+
text: "Hello"
|
|
178
|
+
)
|
|
179
|
+
rescue Sidemail::Error => e
|
|
180
|
+
puts e.message
|
|
181
|
+
if e.http_status # API error
|
|
182
|
+
puts "#{e.http_status} #{e.error_code} #{e.more_info}"
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Response objects
|
|
188
|
+
|
|
189
|
+
Most responses are wrapped in a `Resource` enabling attribute access while remaining hash-like.
|
|
190
|
+
|
|
191
|
+
- Methods return `Resource` wrappers (attribute + hash access); unwrap via `.to_h`.
|
|
192
|
+
- Original JSON available via `.raw`.
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
response = sm.email.get("email-id")
|
|
196
|
+
puts "#{response.email.id} #{response.email.status}"
|
|
197
|
+
puts response.email["id"] # hash-style
|
|
198
|
+
raw_json = response.raw # original JSON mapping
|
|
199
|
+
flat_hash = response.to_h # fully unwrapped hash
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Attachments helper
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
require "sidemail"
|
|
206
|
+
|
|
207
|
+
file_content = File.read("invoice.pdf")
|
|
208
|
+
attachment = Sidemail.file_to_attachment("invoice.pdf", file_content)
|
|
209
|
+
|
|
210
|
+
sm.send_email(
|
|
211
|
+
toAddress: "user@email.com",
|
|
212
|
+
fromAddress: "you@example.com",
|
|
213
|
+
subject: "Invoice",
|
|
214
|
+
text: "Invoice attached.",
|
|
215
|
+
attachments: [attachment]
|
|
216
|
+
)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Auto-pagination
|
|
220
|
+
|
|
221
|
+
List/search methods return a `PaginatedResponse` containing the first page in `result.data`. Iterate across all pages with `auto_paginate`.
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
result = sm.contacts.list(limit: 50)
|
|
225
|
+
|
|
226
|
+
result.auto_paginate.each do |contact|
|
|
227
|
+
puts contact.emailAddress
|
|
228
|
+
end
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Supported auto-paging methods:
|
|
232
|
+
|
|
233
|
+
- `sm.contacts.list`
|
|
234
|
+
- `sm.contacts.query`
|
|
235
|
+
- `sm.email.search`
|
|
236
|
+
- `sm.messenger.list`
|
|
237
|
+
|
|
238
|
+
## Email Methods
|
|
239
|
+
|
|
240
|
+
### Search emails
|
|
241
|
+
|
|
242
|
+
Paginated (supports auto-pagination).
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
result = sm.email.search(
|
|
246
|
+
query: {
|
|
247
|
+
toAddress: "john.doe@example.com",
|
|
248
|
+
status: "delivered",
|
|
249
|
+
templateProps: { foo: "bar" }
|
|
250
|
+
},
|
|
251
|
+
limit: 50
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
puts "First page count: #{result.items.count}"
|
|
255
|
+
result.auto_paginate.each do |email|
|
|
256
|
+
puts "#{email.id} #{email.status}"
|
|
257
|
+
end
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Retrieve a specific email
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
resp = sm.email.get("SIDEMAIL_EMAIL_ID")
|
|
264
|
+
puts resp.email
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Delete a scheduled email
|
|
268
|
+
|
|
269
|
+
Only scheduled (future) emails can be deleted.
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
resp = sm.email.delete("SIDEMAIL_EMAIL_ID")
|
|
273
|
+
puts "Deleted: #{resp.deleted}"
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Contact Methods
|
|
277
|
+
|
|
278
|
+
### Create or update a contact
|
|
279
|
+
|
|
280
|
+
```ruby
|
|
281
|
+
resp = sm.contacts.create_or_update(
|
|
282
|
+
emailAddress: "marry@lightning.com",
|
|
283
|
+
identifier: "123",
|
|
284
|
+
customProps: {
|
|
285
|
+
name: "Marry Lightning"
|
|
286
|
+
# ... more props ...
|
|
287
|
+
}
|
|
288
|
+
)
|
|
289
|
+
puts "Contact status: #{resp.status}"
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Find a contact
|
|
293
|
+
|
|
294
|
+
```ruby
|
|
295
|
+
resp = sm.contacts.find("marry@lightning.com")
|
|
296
|
+
if resp.contact
|
|
297
|
+
puts "Found contact: #{resp.contact.emailAddress}"
|
|
298
|
+
end
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### List all contacts
|
|
302
|
+
|
|
303
|
+
```ruby
|
|
304
|
+
result = sm.contacts.list(limit: 50)
|
|
305
|
+
puts "Has more: #{result.has_more}"
|
|
306
|
+
result.auto_paginate.each do |c|
|
|
307
|
+
puts c.emailAddress
|
|
308
|
+
end
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Query contacts (filtering)
|
|
312
|
+
|
|
313
|
+
```ruby
|
|
314
|
+
result = sm.contacts.query(limit: 100, query: { "customProps.plan" => "pro" })
|
|
315
|
+
result.auto_paginate.each do |c|
|
|
316
|
+
puts c.emailAddress
|
|
317
|
+
end
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Delete a contact
|
|
321
|
+
|
|
322
|
+
```ruby
|
|
323
|
+
resp = sm.contacts.delete("marry@lightning.com")
|
|
324
|
+
puts resp
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Project Methods
|
|
328
|
+
|
|
329
|
+
Linked projects are associated with the parent project of the API key used to initialize Sidemail. After creation, update the design to personalize templates.
|
|
330
|
+
|
|
331
|
+
### Create a linked project
|
|
332
|
+
|
|
333
|
+
```ruby
|
|
334
|
+
project = sm.project.create(name: "Customer X linked project")
|
|
335
|
+
# Important! Save project.apiKey for later use
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Update a linked project
|
|
339
|
+
|
|
340
|
+
```ruby
|
|
341
|
+
updated = sm.project.update(
|
|
342
|
+
name: "New name",
|
|
343
|
+
emailTemplateDesign: {
|
|
344
|
+
logo: {
|
|
345
|
+
sizeWidth: 50,
|
|
346
|
+
href: "https://example.com",
|
|
347
|
+
file: "PHN2ZyBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjIiIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMTIgNS43MmMtMi42MjQtNC41MTctMTAtMy4xOTgtMTAgMi40NjEgMCAzLjcyNSA0LjM0NSA3LjcyNyA5LjMwMyAxMi41NC4xOTQuMTg5LjQ0Ni4yODMuNjk3LjI4M3MuNTAzLS4wOTQuNjk3LS4yODNjNC45NzctNC44MzEgOS4zMDMtOC44MTQgOS4zMDMtMTIuNTQgMC01LjY3OC03LjM5Ni02Ljk0NC0xMC0yLjQ2MXoiIGZpbGwtcnVsZT0ibm9uemVybyIvPjwvc3ZnPg==",
|
|
348
|
+
},
|
|
349
|
+
font: { name: "Acme" },
|
|
350
|
+
colors: { highlight: "#0000FF", isDarkModeEnabled: true },
|
|
351
|
+
unsubscribeText: "Darse de baja",
|
|
352
|
+
footerTextTransactional: "You're receiving these emails because you registered for Acme Inc."
|
|
353
|
+
}
|
|
354
|
+
)
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Get a project
|
|
358
|
+
|
|
359
|
+
```ruby
|
|
360
|
+
project = sm.project.get
|
|
361
|
+
puts "#{project.id} #{project.name}"
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Delete a linked project
|
|
365
|
+
|
|
366
|
+
```ruby
|
|
367
|
+
resp = sm.project.delete
|
|
368
|
+
puts resp
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Messenger API (newsletters)
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
result = sm.messenger.list(limit: 20)
|
|
375
|
+
result.auto_paginate.each do |m|
|
|
376
|
+
puts "#{m.id} #{m.name}"
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
messenger = sm.messenger.get("messenger-id")
|
|
380
|
+
created = sm.messenger.create(subject: "My Messenger", markdown: "Broadcast message...")
|
|
381
|
+
updated = sm.messenger.update("messenger-id", name: "Updated name")
|
|
382
|
+
deleted = sm.messenger.delete("messenger-id")
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
## Sending domains API
|
|
386
|
+
|
|
387
|
+
```ruby
|
|
388
|
+
domains = sm.domains.list
|
|
389
|
+
domain = sm.domains.create(name: "example.com")
|
|
390
|
+
deleted = sm.domains.delete("domain-id")
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## More Info
|
|
394
|
+
|
|
395
|
+
Visit [Sidemail docs](https://sidemail.io/docs) for more information.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "json"
|
|
5
|
+
require "uri"
|
|
6
|
+
require "base64"
|
|
7
|
+
require_relative "paginated_response"
|
|
8
|
+
require_relative "resources/email"
|
|
9
|
+
require_relative "resources/contact"
|
|
10
|
+
require_relative "resources/project"
|
|
11
|
+
require_relative "resources/messenger"
|
|
12
|
+
require_relative "resources/domain"
|
|
13
|
+
|
|
14
|
+
module Sidemail
|
|
15
|
+
class Client
|
|
16
|
+
DEFAULT_BASE_URL = "https://api.sidemail.io/v1"
|
|
17
|
+
|
|
18
|
+
attr_reader :api_key, :base_url, :timeout, :http_client
|
|
19
|
+
|
|
20
|
+
def initialize(api_key: nil, base_url: DEFAULT_BASE_URL, timeout: nil, http_client: nil)
|
|
21
|
+
@api_key = api_key || ENV["SIDEMAIL_API_KEY"]
|
|
22
|
+
raise Sidemail::Error.new("apiKey missing. Provide it as an option or set SIDEMAIL_API_KEY environment variable.") unless @api_key
|
|
23
|
+
@base_url = base_url
|
|
24
|
+
@timeout = timeout
|
|
25
|
+
@http_client = http_client
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def email
|
|
29
|
+
@email ||= Resources::Email.new(self)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def contacts
|
|
33
|
+
@contacts ||= Resources::Contact.new(self)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def project
|
|
37
|
+
@project ||= Resources::Project.new(self)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def messenger
|
|
41
|
+
@messenger ||= Resources::Messenger.new(self)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def domains
|
|
45
|
+
@domains ||= Resources::Domain.new(self)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def send_email(params)
|
|
49
|
+
email.send(params)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def perform_request(path, params: nil, method: :get)
|
|
53
|
+
base = @base_url.end_with?("/") ? @base_url : "#{@base_url}/"
|
|
54
|
+
uri = URI.parse("#{base}#{path}")
|
|
55
|
+
|
|
56
|
+
if method == :get && params
|
|
57
|
+
# Add query params
|
|
58
|
+
existing_query = URI.decode_www_form(uri.query || "")
|
|
59
|
+
new_query = existing_query + params.map { |k, v| [k.to_s, v] }
|
|
60
|
+
uri.query = URI.encode_www_form(new_query)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
http = if @http_client
|
|
64
|
+
@http_client
|
|
65
|
+
else
|
|
66
|
+
client = Net::HTTP.new(uri.host, uri.port)
|
|
67
|
+
client.use_ssl = (uri.scheme == "https")
|
|
68
|
+
if @timeout
|
|
69
|
+
client.read_timeout = @timeout
|
|
70
|
+
client.open_timeout = @timeout
|
|
71
|
+
end
|
|
72
|
+
client
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
request = case method
|
|
76
|
+
when :get
|
|
77
|
+
Net::HTTP::Get.new(uri)
|
|
78
|
+
when :post
|
|
79
|
+
Net::HTTP::Post.new(uri)
|
|
80
|
+
when :delete
|
|
81
|
+
Net::HTTP::Delete.new(uri)
|
|
82
|
+
when :patch
|
|
83
|
+
Net::HTTP::Patch.new(uri)
|
|
84
|
+
else
|
|
85
|
+
raise ArgumentError, "Unknown method: #{method}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
request["Authorization"] = "Bearer #{@api_key}"
|
|
89
|
+
request["Content-Type"] = "application/json"
|
|
90
|
+
request["User-Agent"] = "sidemail-sdk-ruby/#{Sidemail::VERSION}"
|
|
91
|
+
|
|
92
|
+
if [:post, :patch].include?(method) && params
|
|
93
|
+
request.body = params.to_json
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
response = http.request(request)
|
|
97
|
+
|
|
98
|
+
content_type = response["content-type"]
|
|
99
|
+
|
|
100
|
+
if content_type && content_type.include?("application/json")
|
|
101
|
+
body = JSON.parse(response.body)
|
|
102
|
+
else
|
|
103
|
+
body = response.body
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
107
|
+
message = if body.is_a?(Hash)
|
|
108
|
+
body["developerMessage"]
|
|
109
|
+
else
|
|
110
|
+
body.to_s.empty? ? response.message : body
|
|
111
|
+
end
|
|
112
|
+
error_code = body.is_a?(Hash) ? body["errorCode"] : nil
|
|
113
|
+
more_info = body.is_a?(Hash) ? body["moreInfo"] : nil
|
|
114
|
+
|
|
115
|
+
raise Sidemail::Error.new(
|
|
116
|
+
message,
|
|
117
|
+
http_status: response.code.to_i,
|
|
118
|
+
error_code: error_code,
|
|
119
|
+
more_info: more_info
|
|
120
|
+
)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
body
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sidemail
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
attr_reader :http_status, :error_code, :more_info
|
|
6
|
+
|
|
7
|
+
def initialize(message, http_status: nil, error_code: nil, more_info: nil)
|
|
8
|
+
super(message)
|
|
9
|
+
@http_status = http_status
|
|
10
|
+
@error_code = error_code
|
|
11
|
+
@more_info = more_info
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "resource"
|
|
4
|
+
|
|
5
|
+
module Sidemail
|
|
6
|
+
class PaginatedResponse
|
|
7
|
+
include Enumerable
|
|
8
|
+
|
|
9
|
+
attr_reader :has_more, :pagination_cursor_next, :pagination_cursor_prev, :raw
|
|
10
|
+
|
|
11
|
+
def initialize(response_body, client, path, params, method)
|
|
12
|
+
@raw = response_body
|
|
13
|
+
@items = (response_body["data"] || []).map { |item| Resource.new(item) }
|
|
14
|
+
@has_more = response_body["hasMore"]
|
|
15
|
+
@pagination_cursor_next = response_body["paginationCursorNext"]
|
|
16
|
+
@pagination_cursor_prev = response_body["paginationCursorPrev"]
|
|
17
|
+
@client = client
|
|
18
|
+
@path = path
|
|
19
|
+
@params = params
|
|
20
|
+
@method = method
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def inspect
|
|
24
|
+
@raw.inspect
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def to_s
|
|
28
|
+
@raw.to_s
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def [](key)
|
|
32
|
+
@raw[key.to_s]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def method_missing(method_name, *args, &block)
|
|
36
|
+
if @raw.key?(method_name.to_s)
|
|
37
|
+
@raw[method_name.to_s]
|
|
38
|
+
else
|
|
39
|
+
super
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
44
|
+
@raw.key?(method_name.to_s) || super
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def items
|
|
48
|
+
@items
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def data
|
|
52
|
+
@items
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def each(&block)
|
|
56
|
+
return to_enum(:each) unless block_given?
|
|
57
|
+
|
|
58
|
+
@items.each do |item|
|
|
59
|
+
yield item
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def auto_paginate
|
|
64
|
+
return to_enum(:auto_paginate) unless block_given?
|
|
65
|
+
|
|
66
|
+
# Yield current page items
|
|
67
|
+
each { |item| yield item }
|
|
68
|
+
|
|
69
|
+
current_cursor = @pagination_cursor_next
|
|
70
|
+
|
|
71
|
+
while current_cursor
|
|
72
|
+
# Fetch next page
|
|
73
|
+
next_params = @params.dup
|
|
74
|
+
if @method == :get
|
|
75
|
+
next_params[:paginationCursorNext] = current_cursor
|
|
76
|
+
else
|
|
77
|
+
next_params["paginationCursorNext"] = current_cursor
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
response = @client.perform_request(@path, params: next_params, method: @method)
|
|
81
|
+
|
|
82
|
+
# Yield items from the new page
|
|
83
|
+
if response["data"]
|
|
84
|
+
response["data"].each { |item| yield Resource.new(item) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
current_cursor = response["paginationCursorNext"]
|
|
88
|
+
break unless response["hasMore"]
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sidemail
|
|
4
|
+
class Resource
|
|
5
|
+
def initialize(data)
|
|
6
|
+
@data = data
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def [](key)
|
|
10
|
+
value = @data[key.to_s]
|
|
11
|
+
wrap(value)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_h
|
|
15
|
+
@data
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def raw
|
|
19
|
+
@data
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def inspect
|
|
23
|
+
@data.inspect
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_s
|
|
27
|
+
@data.to_s
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def method_missing(method_name, *args, &block)
|
|
31
|
+
key = method_name.to_s
|
|
32
|
+
if @data.key?(key)
|
|
33
|
+
wrap(@data[key])
|
|
34
|
+
else
|
|
35
|
+
super
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
40
|
+
@data.key?(method_name.to_s) || super
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def wrap(value)
|
|
46
|
+
if value.is_a?(Hash)
|
|
47
|
+
Resource.new(value)
|
|
48
|
+
elsif value.is_a?(Array)
|
|
49
|
+
value.map { |v| wrap(v) }
|
|
50
|
+
else
|
|
51
|
+
value
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sidemail
|
|
4
|
+
module Resources
|
|
5
|
+
class Contact
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create_or_update(params)
|
|
11
|
+
raise Sidemail::Error.new("Missing contact data") unless params
|
|
12
|
+
response = @client.perform_request("contacts", params: params, method: :post)
|
|
13
|
+
Resource.new(response)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def find(email_address)
|
|
17
|
+
raise Sidemail::Error.new("Missing emailAddress") unless email_address
|
|
18
|
+
response = @client.perform_request("contacts/#{email_address}", method: :get)
|
|
19
|
+
Resource.new(response)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def list(params = {})
|
|
23
|
+
response = @client.perform_request("contacts", params: params, method: :get)
|
|
24
|
+
PaginatedResponse.new(response, @client, "contacts", params, :get)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def query(params = {})
|
|
28
|
+
list(params)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def delete(email_address)
|
|
32
|
+
response = @client.perform_request("contacts/#{email_address}", method: :delete)
|
|
33
|
+
Resource.new(response)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sidemail
|
|
4
|
+
module Resources
|
|
5
|
+
class Domain
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def list(params = {})
|
|
11
|
+
response = @client.perform_request("domains", params: params, method: :get)
|
|
12
|
+
PaginatedResponse.new(response, @client, "domains", params, :get)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create(params)
|
|
16
|
+
response = @client.perform_request("domains", params: params, method: :post)
|
|
17
|
+
Resource.new(response)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def delete(id)
|
|
21
|
+
response = @client.perform_request("domains/#{id}", method: :delete)
|
|
22
|
+
Resource.new(response)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sidemail
|
|
4
|
+
module Resources
|
|
5
|
+
class Email
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def send(params)
|
|
11
|
+
response = @client.perform_request("email/send", params: params, method: :post)
|
|
12
|
+
Resource.new(response)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def search(params = {})
|
|
16
|
+
response = @client.perform_request("email/search", params: params, method: :post)
|
|
17
|
+
PaginatedResponse.new(response, @client, "email/search", params, :post)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def get(id)
|
|
21
|
+
response = @client.perform_request("email/#{id}", method: :get)
|
|
22
|
+
Resource.new(response)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def delete(id)
|
|
26
|
+
response = @client.perform_request("email/#{id}", method: :delete)
|
|
27
|
+
Resource.new(response)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sidemail
|
|
4
|
+
module Resources
|
|
5
|
+
class Messenger
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def list(params = {})
|
|
11
|
+
response = @client.perform_request("messenger", params: params, method: :get)
|
|
12
|
+
PaginatedResponse.new(response, @client, "messenger", params, :get)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get(id)
|
|
16
|
+
response = @client.perform_request("messenger/#{id}", method: :get)
|
|
17
|
+
Resource.new(response)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create(params)
|
|
21
|
+
response = @client.perform_request("messenger", params: params, method: :post)
|
|
22
|
+
Resource.new(response)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def update(id, params)
|
|
26
|
+
response = @client.perform_request("messenger/#{id}", params: params, method: :patch)
|
|
27
|
+
Resource.new(response)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def delete(id)
|
|
31
|
+
response = @client.perform_request("messenger/#{id}", method: :delete)
|
|
32
|
+
Resource.new(response)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sidemail
|
|
4
|
+
module Resources
|
|
5
|
+
class Project
|
|
6
|
+
def initialize(client)
|
|
7
|
+
@client = client
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create(params)
|
|
11
|
+
response = @client.perform_request("project", params: params, method: :post)
|
|
12
|
+
Resource.new(response)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get
|
|
16
|
+
response = @client.perform_request("project", method: :get)
|
|
17
|
+
Resource.new(response)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def update(params)
|
|
21
|
+
response = @client.perform_request("project", params: params, method: :patch)
|
|
22
|
+
Resource.new(response)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def delete
|
|
26
|
+
response = @client.perform_request("project", method: :delete)
|
|
27
|
+
Resource.new(response)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/sidemail.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "sidemail/version"
|
|
4
|
+
require_relative "sidemail/error"
|
|
5
|
+
require_relative "sidemail/client"
|
|
6
|
+
|
|
7
|
+
module Sidemail
|
|
8
|
+
class << self
|
|
9
|
+
attr_accessor :api_key
|
|
10
|
+
|
|
11
|
+
def new(api_key: nil, **options)
|
|
12
|
+
Client.new(api_key: api_key || self.api_key, **options)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def file_to_attachment(name, content)
|
|
16
|
+
{
|
|
17
|
+
name: name,
|
|
18
|
+
content: Base64.strict_encode64(content)
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sidemail
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sidemail
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-12-16 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: base64
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: fiddle
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rspec
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '3.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rake
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '13.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '13.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: webmock
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '3.18'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '3.18'
|
|
83
|
+
description: Official Sidemail.io Ruby library providing convenient access to the
|
|
84
|
+
Sidemail API.
|
|
85
|
+
email:
|
|
86
|
+
- support@sidemail.io
|
|
87
|
+
executables: []
|
|
88
|
+
extensions: []
|
|
89
|
+
extra_rdoc_files: []
|
|
90
|
+
files:
|
|
91
|
+
- README.md
|
|
92
|
+
- lib/sidemail.rb
|
|
93
|
+
- lib/sidemail/client.rb
|
|
94
|
+
- lib/sidemail/error.rb
|
|
95
|
+
- lib/sidemail/paginated_response.rb
|
|
96
|
+
- lib/sidemail/resource.rb
|
|
97
|
+
- lib/sidemail/resources/contact.rb
|
|
98
|
+
- lib/sidemail/resources/domain.rb
|
|
99
|
+
- lib/sidemail/resources/email.rb
|
|
100
|
+
- lib/sidemail/resources/messenger.rb
|
|
101
|
+
- lib/sidemail/resources/project.rb
|
|
102
|
+
- lib/sidemail/version.rb
|
|
103
|
+
homepage: https://sidemail.io
|
|
104
|
+
licenses:
|
|
105
|
+
- MIT
|
|
106
|
+
metadata: {}
|
|
107
|
+
post_install_message:
|
|
108
|
+
rdoc_options: []
|
|
109
|
+
require_paths:
|
|
110
|
+
- lib
|
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - ">="
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: 2.6.0
|
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
117
|
+
requirements:
|
|
118
|
+
- - ">="
|
|
119
|
+
- !ruby/object:Gem::Version
|
|
120
|
+
version: '0'
|
|
121
|
+
requirements: []
|
|
122
|
+
rubygems_version: 3.4.19
|
|
123
|
+
signing_key:
|
|
124
|
+
specification_version: 4
|
|
125
|
+
summary: Official Sidemail.io Ruby SDK
|
|
126
|
+
test_files: []
|