possinote 1.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +547 -0
- data/lib/possinote/client.rb +63 -0
- data/lib/possinote/email.rb +47 -0
- data/lib/possinote/errors.rb +19 -0
- data/lib/possinote/scheduling.rb +63 -0
- data/lib/possinote/sms.rb +82 -0
- data/lib/possinote.rb +18 -0
- metadata +140 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7366a61c6be2b7e60f16cff7f9052d50ecdc5fb219a26df77d39e296ec1310fe
|
|
4
|
+
data.tar.gz: ceaa4bf096a171762f26cfa8fdb55a8e231574ef2cc7a69997f604cd022ea9d6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4afed04872ab0b3a7cfe33571703cb98807b5faade7bdfa5047f88c41e4bc177ac44a3f75302cbd7d230e3ec82f1cea527590cfa364a1b091f5cdd0276ee4b7d
|
|
7
|
+
data.tar.gz: 86a358c987ddb7dec4d1484b7262a24e5e9b5afa8688dba0751702d92812cca1e7dd5681c48650c095d34e800c8a91cb7694517a4ec2e900e15fd58fbcb1242d
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 PossiNote Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
# Possinote Ruby SDK
|
|
2
|
+
|
|
3
|
+
Official Ruby SDK for the PossiNote API - Send SMS, emails, and schedule messages with ease.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add this line to your application's Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem 'possinote'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
And then execute:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
$ bundle install
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or install it yourself as:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
$ gem install possinote
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
require 'possinote'
|
|
29
|
+
|
|
30
|
+
# Initialize the client with your API key
|
|
31
|
+
client = Possinote::Client.new(api_key: 'your_api_key_here')
|
|
32
|
+
|
|
33
|
+
# Send a single SMS
|
|
34
|
+
response = client.sms.send(
|
|
35
|
+
to: '+233244123456',
|
|
36
|
+
message: 'Hello from Possinote!',
|
|
37
|
+
sender_id: 'YourSenderID'
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Send a single email
|
|
41
|
+
response = client.email.send(
|
|
42
|
+
recipient: 'user@example.com',
|
|
43
|
+
subject: 'Welcome to Possinote',
|
|
44
|
+
content: '<h1>Hello!</h1><p>Welcome to our platform.</p>',
|
|
45
|
+
sender_name: 'Your Company'
|
|
46
|
+
)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## API Reference
|
|
50
|
+
|
|
51
|
+
### Authentication
|
|
52
|
+
|
|
53
|
+
All API requests require authentication using your API key:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
client = Possinote::Client.new(api_key: 'your_api_key_here')
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### SMS Operations
|
|
60
|
+
|
|
61
|
+
#### Send Single SMS
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
response = client.sms.send(
|
|
65
|
+
to: '+233244123456',
|
|
66
|
+
message: 'Your message here',
|
|
67
|
+
sender_id: 'YourSenderID'
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Response
|
|
71
|
+
{
|
|
72
|
+
"success" => true,
|
|
73
|
+
"data" => {
|
|
74
|
+
"message_id" => "msg_123456789",
|
|
75
|
+
"to" => "+233244123456",
|
|
76
|
+
"status" => "queued",
|
|
77
|
+
"cost" => 1.0,
|
|
78
|
+
"created_at" => "2025-08-11T11:30:00Z"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### Send Bulk SMS
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
response = client.sms.send_bulk(
|
|
87
|
+
sender_id: 'YourSenderID',
|
|
88
|
+
messages: [
|
|
89
|
+
{ to: '+233244123456', message: 'Message 1' },
|
|
90
|
+
{ to: '+233244123457', message: 'Message 2' }
|
|
91
|
+
]
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Response
|
|
95
|
+
{
|
|
96
|
+
"success" => true,
|
|
97
|
+
"data" => {
|
|
98
|
+
"batch_id" => "batch_123456789",
|
|
99
|
+
"total_messages" => 2,
|
|
100
|
+
"successful" => 2,
|
|
101
|
+
"failed" => 0,
|
|
102
|
+
"total_cost" => 2.0,
|
|
103
|
+
"messages" => [
|
|
104
|
+
{ "message_id" => "msg_1", "to" => "+233244123456", "status" => "queued" },
|
|
105
|
+
{ "message_id" => "msg_2", "to" => "+233244123457", "status" => "queued" }
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### Schedule Single SMS
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
response = client.sms.schedule(
|
|
115
|
+
recipient: '+233244123456',
|
|
116
|
+
message: 'Scheduled message',
|
|
117
|
+
sender_id: 'YourSenderID',
|
|
118
|
+
scheduled_at: '2025-08-11T12:00:00Z'
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Response
|
|
122
|
+
{
|
|
123
|
+
"success" => true,
|
|
124
|
+
"data" => {
|
|
125
|
+
"id" => "schedule_123456789",
|
|
126
|
+
"recipient" => "+233244123456",
|
|
127
|
+
"message" => "Scheduled message",
|
|
128
|
+
"scheduled_at" => "2025-08-11T12:00:00Z",
|
|
129
|
+
"status" => "pending",
|
|
130
|
+
"cost" => "1.0"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
#### Schedule Bulk SMS
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
response = client.sms.schedule_bulk(
|
|
139
|
+
sender_id: 'YourSenderID',
|
|
140
|
+
messages: [
|
|
141
|
+
{ recipient: '+233244123456', message: 'Scheduled message 1' },
|
|
142
|
+
{ recipient: '+233244123457', message: 'Scheduled message 2' }
|
|
143
|
+
],
|
|
144
|
+
scheduled_at: '2025-08-11T12:00:00Z'
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Response
|
|
148
|
+
{
|
|
149
|
+
"success" => true,
|
|
150
|
+
"data" => {
|
|
151
|
+
"batch_id" => "batch_123456789",
|
|
152
|
+
"scheduled_count" => 2,
|
|
153
|
+
"total_cost" => 2.0,
|
|
154
|
+
"scheduled_at" => "2025-08-11T12:00:00Z",
|
|
155
|
+
"messages" => [
|
|
156
|
+
{ "id" => "schedule_1", "recipient" => "+233244123456", "status" => "pending" },
|
|
157
|
+
{ "id" => "schedule_2", "recipient" => "+233244123457", "status" => "pending" }
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Email Operations
|
|
164
|
+
|
|
165
|
+
#### Send Single Email
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
response = client.email.send(
|
|
169
|
+
recipient: 'user@example.com',
|
|
170
|
+
subject: 'Welcome Email',
|
|
171
|
+
content: '<h1>Welcome!</h1><p>Thank you for joining us.</p>',
|
|
172
|
+
sender_name: 'Your Company'
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Response
|
|
176
|
+
{
|
|
177
|
+
"success" => true,
|
|
178
|
+
"message" => "Email queued for delivery",
|
|
179
|
+
"recipient" => "user@example.com",
|
|
180
|
+
"message_id" => "email_123456789"
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### Send Bulk Email
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
response = client.email.send_bulk(
|
|
188
|
+
subject: 'Newsletter',
|
|
189
|
+
content: '<h1>Newsletter</h1><p>This is our monthly newsletter.</p>',
|
|
190
|
+
recipients: ['user1@example.com', 'user2@example.com'],
|
|
191
|
+
sender_name: 'Your Company'
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# Response
|
|
195
|
+
{
|
|
196
|
+
"success" => true,
|
|
197
|
+
"message" => "Bulk emails queued for delivery",
|
|
198
|
+
"queued_count" => 2,
|
|
199
|
+
"total_count" => 2,
|
|
200
|
+
"batch_id" => "batch_123456789",
|
|
201
|
+
"emails" => [
|
|
202
|
+
{ "message_id" => "email_1", "recipient" => "user1@example.com", "status" => "queued" },
|
|
203
|
+
{ "message_id" => "email_2", "recipient" => "user2@example.com", "status" => "queued" }
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Scheduling Operations
|
|
209
|
+
|
|
210
|
+
#### Schedule Single Email
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
response = client.scheduling.schedule_email(
|
|
214
|
+
recipient: 'user@example.com',
|
|
215
|
+
subject: 'Scheduled Email',
|
|
216
|
+
content: '<h1>Scheduled Content</h1>',
|
|
217
|
+
scheduled_at: '2025-08-11T12:00:00Z',
|
|
218
|
+
sender_name: 'Your Company'
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Response
|
|
222
|
+
{
|
|
223
|
+
"success" => true,
|
|
224
|
+
"data" => {
|
|
225
|
+
"id" => "email_schedule_123456789",
|
|
226
|
+
"recipient" => "user@example.com",
|
|
227
|
+
"subject" => "Scheduled Email",
|
|
228
|
+
"scheduled_at" => "2025-08-11T12:00:00Z",
|
|
229
|
+
"status" => "pending",
|
|
230
|
+
"cost" => "1.0"
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
#### Schedule Bulk Individual Emails
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
response = client.scheduling.schedule_multiple_emails([
|
|
239
|
+
{
|
|
240
|
+
recipient: 'user1@example.com',
|
|
241
|
+
subject: 'Personalized Email 1',
|
|
242
|
+
content: '<h1>Hello User 1!</h1>',
|
|
243
|
+
scheduled_at: '2025-08-11T12:00:00Z',
|
|
244
|
+
sender_name: 'Your Company'
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
recipient: 'user2@example.com',
|
|
248
|
+
subject: 'Personalized Email 2',
|
|
249
|
+
content: '<h1>Hello User 2!</h1>',
|
|
250
|
+
scheduled_at: '2025-08-11T12:00:00Z',
|
|
251
|
+
sender_name: 'Your Company'
|
|
252
|
+
}
|
|
253
|
+
])
|
|
254
|
+
|
|
255
|
+
# Response
|
|
256
|
+
{
|
|
257
|
+
"success" => true,
|
|
258
|
+
"data" => {
|
|
259
|
+
"batch_id" => "batch_123456789",
|
|
260
|
+
"total_scheduled" => 2,
|
|
261
|
+
"total_cost" => 2.0,
|
|
262
|
+
"scheduled_emails" => [
|
|
263
|
+
{ "id" => "email_1", "recipient" => "user1@example.com", "status" => "pending" },
|
|
264
|
+
{ "id" => "email_2", "recipient" => "user2@example.com", "status" => "pending" }
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Framework Integration
|
|
271
|
+
|
|
272
|
+
### Rails Integration
|
|
273
|
+
|
|
274
|
+
#### 1. Add to Gemfile
|
|
275
|
+
```ruby
|
|
276
|
+
# Gemfile
|
|
277
|
+
gem 'possinote'
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### 2. Configure in Application
|
|
281
|
+
```ruby
|
|
282
|
+
# config/initializers/possinote.rb
|
|
283
|
+
require 'possinote'
|
|
284
|
+
|
|
285
|
+
# Initialize the client
|
|
286
|
+
POSSINOTE_CLIENT = Possinote::Client.new(
|
|
287
|
+
api_key: Rails.application.credentials.possinote[:api_key]
|
|
288
|
+
)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
#### 3. Use in Controllers
|
|
292
|
+
```ruby
|
|
293
|
+
# app/controllers/notifications_controller.rb
|
|
294
|
+
class NotificationsController < ApplicationController
|
|
295
|
+
def send_sms
|
|
296
|
+
begin
|
|
297
|
+
response = POSSINOTE_CLIENT.sms.send(
|
|
298
|
+
to: params[:phone_number],
|
|
299
|
+
message: params[:message],
|
|
300
|
+
sender_id: 'YourBrand'
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
if response['success']
|
|
304
|
+
render json: { message: 'SMS sent successfully', data: response['data'] }
|
|
305
|
+
else
|
|
306
|
+
render json: { error: 'Failed to send SMS' }, status: :unprocessable_entity
|
|
307
|
+
end
|
|
308
|
+
rescue Possinote::AuthenticationError => e
|
|
309
|
+
render json: { error: 'Authentication failed' }, status: :unauthorized
|
|
310
|
+
rescue Possinote::PaymentRequiredError => e
|
|
311
|
+
render json: { error: 'Insufficient credits' }, status: :payment_required
|
|
312
|
+
rescue Possinote::ValidationError => e
|
|
313
|
+
render json: { error: e.message }, status: :bad_request
|
|
314
|
+
rescue => e
|
|
315
|
+
render json: { error: 'An error occurred' }, status: :internal_server_error
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def send_bulk_sms
|
|
320
|
+
begin
|
|
321
|
+
response = POSSINOTE_CLIENT.sms.send_bulk(
|
|
322
|
+
sender_id: 'YourBrand',
|
|
323
|
+
messages: params[:messages] # Array of { to: phone, message: text }
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
render json: { message: 'Bulk SMS sent', data: response['data'] }
|
|
327
|
+
rescue => e
|
|
328
|
+
render json: { error: e.message }, status: :internal_server_error
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def send_email
|
|
333
|
+
begin
|
|
334
|
+
response = POSSINOTE_CLIENT.email.send(
|
|
335
|
+
recipient: params[:email],
|
|
336
|
+
subject: params[:subject],
|
|
337
|
+
content: params[:content],
|
|
338
|
+
sender_name: 'Your Company'
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
render json: { message: 'Email sent successfully', data: response }
|
|
342
|
+
rescue => e
|
|
343
|
+
render json: { error: e.message }, status: :internal_server_error
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
#### 4. Use in Services
|
|
350
|
+
```ruby
|
|
351
|
+
# app/services/notification_service.rb
|
|
352
|
+
class NotificationService
|
|
353
|
+
def initialize
|
|
354
|
+
@client = POSSINOTE_CLIENT
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def send_welcome_sms(user)
|
|
358
|
+
@client.sms.send(
|
|
359
|
+
to: user.phone_number,
|
|
360
|
+
message: "Welcome #{user.name}! Your account has been created successfully.",
|
|
361
|
+
sender_id: 'YourBrand'
|
|
362
|
+
)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def send_password_reset_email(user, reset_token)
|
|
366
|
+
@client.email.send(
|
|
367
|
+
recipient: user.email,
|
|
368
|
+
subject: 'Password Reset Request',
|
|
369
|
+
content: generate_password_reset_html(user, reset_token),
|
|
370
|
+
sender_name: 'Your Company'
|
|
371
|
+
)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def send_bulk_newsletter(users, newsletter_content)
|
|
375
|
+
messages = users.map do |user|
|
|
376
|
+
{
|
|
377
|
+
to: user.phone_number,
|
|
378
|
+
message: "Newsletter: #{newsletter_content[:title]}"
|
|
379
|
+
}
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
@client.sms.send_bulk(
|
|
383
|
+
sender_id: 'YourBrand',
|
|
384
|
+
messages: messages
|
|
385
|
+
)
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def schedule_reminder_email(user, appointment)
|
|
389
|
+
@client.scheduling.schedule_email(
|
|
390
|
+
recipient: user.email,
|
|
391
|
+
subject: 'Appointment Reminder',
|
|
392
|
+
content: generate_appointment_reminder_html(appointment),
|
|
393
|
+
scheduled_at: appointment.datetime - 1.hour,
|
|
394
|
+
sender_name: 'Your Company'
|
|
395
|
+
)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
private
|
|
399
|
+
|
|
400
|
+
def generate_password_reset_html(user, token)
|
|
401
|
+
<<~HTML
|
|
402
|
+
<h1>Password Reset Request</h1>
|
|
403
|
+
<p>Hello #{user.name},</p>
|
|
404
|
+
<p>You requested a password reset. Click the link below to reset your password:</p>
|
|
405
|
+
<a href="#{Rails.application.routes.url_helpers.reset_password_url(token: token)}">
|
|
406
|
+
Reset Password
|
|
407
|
+
</a>
|
|
408
|
+
<p>This link will expire in 1 hour.</p>
|
|
409
|
+
HTML
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def generate_appointment_reminder_html(appointment)
|
|
413
|
+
<<~HTML
|
|
414
|
+
<h1>Appointment Reminder</h1>
|
|
415
|
+
<p>Hello #{appointment.user.name},</p>
|
|
416
|
+
<p>This is a reminder for your appointment:</p>
|
|
417
|
+
<ul>
|
|
418
|
+
<li><strong>Date:</strong> #{appointment.datetime.strftime('%B %d, %Y')}</li>
|
|
419
|
+
<li><strong>Time:</strong> #{appointment.datetime.strftime('%I:%M %p')}</li>
|
|
420
|
+
<li><strong>Location:</strong> #{appointment.location}</li>
|
|
421
|
+
</ul>
|
|
422
|
+
HTML
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
#### 5. Use in Background Jobs
|
|
428
|
+
```ruby
|
|
429
|
+
# app/jobs/send_notification_job.rb
|
|
430
|
+
class SendNotificationJob < ApplicationJob
|
|
431
|
+
queue_as :default
|
|
432
|
+
|
|
433
|
+
def perform(notification_type, user_id, options = {})
|
|
434
|
+
user = User.find(user_id)
|
|
435
|
+
service = NotificationService.new
|
|
436
|
+
|
|
437
|
+
case notification_type
|
|
438
|
+
when 'welcome_sms'
|
|
439
|
+
service.send_welcome_sms(user)
|
|
440
|
+
when 'password_reset_email'
|
|
441
|
+
service.send_password_reset_email(user, options[:reset_token])
|
|
442
|
+
when 'bulk_newsletter'
|
|
443
|
+
service.send_bulk_newsletter(options[:users], options[:content])
|
|
444
|
+
when 'scheduled_reminder'
|
|
445
|
+
service.schedule_reminder_email(user, options[:appointment])
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# Usage in controller
|
|
451
|
+
SendNotificationJob.perform_later('welcome_sms', user.id)
|
|
452
|
+
SendNotificationJob.perform_later('password_reset_email', user.id, reset_token: token)
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
#### 6. Environment Configuration
|
|
456
|
+
```ruby
|
|
457
|
+
# config/credentials.yml.enc
|
|
458
|
+
possinote:
|
|
459
|
+
api_key: your_api_key_here
|
|
460
|
+
|
|
461
|
+
# Or use environment variables
|
|
462
|
+
# config/initializers/possinote.rb
|
|
463
|
+
POSSINOTE_CLIENT = Possinote::Client.new(
|
|
464
|
+
api_key: ENV['POSSINOTE_API_KEY']
|
|
465
|
+
)
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
## Error Handling
|
|
469
|
+
|
|
470
|
+
The SDK provides specific exception classes for different error types:
|
|
471
|
+
|
|
472
|
+
```ruby
|
|
473
|
+
begin
|
|
474
|
+
response = client.sms.send(to: '+233244123456', message: 'Hello', sender_id: 'SenderID')
|
|
475
|
+
rescue Possinote::AuthenticationError => e
|
|
476
|
+
puts "Authentication failed: #{e.message}"
|
|
477
|
+
rescue Possinote::PaymentRequiredError => e
|
|
478
|
+
puts "Payment required: #{e.message}"
|
|
479
|
+
rescue Possinote::RateLimitError => e
|
|
480
|
+
puts "Rate limit exceeded: #{e.message}"
|
|
481
|
+
rescue Possinote::ValidationError => e
|
|
482
|
+
puts "Validation error: #{e.message}"
|
|
483
|
+
rescue Possinote::APIError => e
|
|
484
|
+
puts "API error: #{e.message}"
|
|
485
|
+
end
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Error Types
|
|
489
|
+
|
|
490
|
+
- `Possinote::AuthenticationError` - Invalid API key (401)
|
|
491
|
+
- `Possinote::PaymentRequiredError` - Insufficient credits (402)
|
|
492
|
+
- `Possinote::RateLimitError` - Rate limit exceeded (429)
|
|
493
|
+
- `Possinote::ValidationError` - Invalid request data (400)
|
|
494
|
+
- `Possinote::APIError` - Other API errors
|
|
495
|
+
|
|
496
|
+
## Configuration
|
|
497
|
+
|
|
498
|
+
### Base URL
|
|
499
|
+
|
|
500
|
+
The SDK uses the production API by default. For testing, you can modify the base URL:
|
|
501
|
+
|
|
502
|
+
```ruby
|
|
503
|
+
# In lib/possinote/client.rb
|
|
504
|
+
BASE_URI = 'https://notifyapi.possitech.net/api/v1'
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Timeout Settings
|
|
508
|
+
|
|
509
|
+
HTTP requests use default timeout settings. You can customize them in the client:
|
|
510
|
+
|
|
511
|
+
```ruby
|
|
512
|
+
# The SDK uses HTTParty defaults
|
|
513
|
+
# You can modify timeout settings in lib/possinote/client.rb
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
## Requirements
|
|
517
|
+
|
|
518
|
+
- Ruby >= 2.6.0
|
|
519
|
+
- HTTParty gem
|
|
520
|
+
- JSON gem
|
|
521
|
+
|
|
522
|
+
## Development
|
|
523
|
+
|
|
524
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
525
|
+
|
|
526
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
527
|
+
|
|
528
|
+
## Contributing
|
|
529
|
+
|
|
530
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/possitech/possinote-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/possitech/possinote-ruby/blob/main/CODE_OF_CONDUCT.md).
|
|
531
|
+
|
|
532
|
+
## License
|
|
533
|
+
|
|
534
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
535
|
+
|
|
536
|
+
## Support
|
|
537
|
+
|
|
538
|
+
For support, email support@possitech.net or visit our [documentation](https://docs.possitech.net).
|
|
539
|
+
|
|
540
|
+
## Changelog
|
|
541
|
+
|
|
542
|
+
### 1.0.0
|
|
543
|
+
- Initial release
|
|
544
|
+
- SMS sending and scheduling
|
|
545
|
+
- Email sending and scheduling
|
|
546
|
+
- Comprehensive error handling
|
|
547
|
+
- Full API coverage
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Possinote
|
|
2
|
+
class Client
|
|
3
|
+
include HTTParty
|
|
4
|
+
|
|
5
|
+
base_uri 'https://notifyapi.possitech.net/api/v1'
|
|
6
|
+
format :json
|
|
7
|
+
|
|
8
|
+
attr_reader :api_key
|
|
9
|
+
|
|
10
|
+
def initialize(api_key:)
|
|
11
|
+
@api_key = api_key
|
|
12
|
+
self.class.headers 'Authorization' => "Bearer #{api_key}"
|
|
13
|
+
self.class.headers 'Content-Type' => 'application/json'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def sms
|
|
17
|
+
@sms ||= SMS.new(self)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def email
|
|
21
|
+
@email ||= Email.new(self)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def scheduling
|
|
25
|
+
@scheduling ||= Scheduling.new(self)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def get(path, options = {})
|
|
29
|
+
handle_response(self.class.get(path, options))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def post(path, options = {})
|
|
33
|
+
handle_response(self.class.post(path, options))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def put(path, options = {})
|
|
37
|
+
handle_response(self.class.put(path, options))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def delete(path, options = {})
|
|
41
|
+
handle_response(self.class.delete(path, options))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def handle_response(response)
|
|
47
|
+
case response.code
|
|
48
|
+
when 200, 201
|
|
49
|
+
response.parsed_response
|
|
50
|
+
when 401
|
|
51
|
+
raise Possinote::AuthenticationError, "Invalid API key"
|
|
52
|
+
when 402
|
|
53
|
+
raise Possinote::PaymentRequiredError, response.parsed_response['error'] || "Payment required"
|
|
54
|
+
when 429
|
|
55
|
+
raise Possinote::RateLimitError, "Rate limit exceeded"
|
|
56
|
+
when 400
|
|
57
|
+
raise Possinote::ValidationError, response.parsed_response['error'] || "Bad request"
|
|
58
|
+
else
|
|
59
|
+
raise Possinote::APIError, "API request failed with status #{response.code}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module Possinote
|
|
2
|
+
class Email
|
|
3
|
+
def initialize(client)
|
|
4
|
+
@client = client
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Send a single email
|
|
8
|
+
def send(recipient:, subject:, content:, sender_name: nil)
|
|
9
|
+
payload = {
|
|
10
|
+
recipient: recipient,
|
|
11
|
+
subject: subject,
|
|
12
|
+
content: content
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# Add sender_name only if provided
|
|
16
|
+
payload[:sender_name] = sender_name if sender_name
|
|
17
|
+
|
|
18
|
+
@client.post('/emails/send', body: payload.to_json)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Send bulk email
|
|
22
|
+
def send_bulk(subject:, content:, recipients:, sender_name: nil)
|
|
23
|
+
payload = {
|
|
24
|
+
subject: subject,
|
|
25
|
+
content: content,
|
|
26
|
+
recipients: recipients
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Add sender_name only if provided
|
|
30
|
+
payload[:sender_name] = sender_name if sender_name
|
|
31
|
+
|
|
32
|
+
@client.post('/emails/bulk', body: payload.to_json)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Get email history
|
|
36
|
+
def history(params = {})
|
|
37
|
+
query_params = build_query_params(params)
|
|
38
|
+
@client.get("/emails/history?#{query_params}")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def build_query_params(params)
|
|
44
|
+
params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Possinote
|
|
2
|
+
module Errors
|
|
3
|
+
class BaseError < StandardError
|
|
4
|
+
attr_reader :code, :response
|
|
5
|
+
|
|
6
|
+
def initialize(message = nil, code = nil, response = nil)
|
|
7
|
+
super(message)
|
|
8
|
+
@code = code
|
|
9
|
+
@response = response
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class AuthenticationError < BaseError; end
|
|
14
|
+
class PaymentRequiredError < BaseError; end
|
|
15
|
+
class RateLimitError < BaseError; end
|
|
16
|
+
class ValidationError < BaseError; end
|
|
17
|
+
class APIError < BaseError; end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Possinote
|
|
2
|
+
class Scheduling
|
|
3
|
+
def initialize(client)
|
|
4
|
+
@client = client
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Schedule a single email
|
|
8
|
+
def schedule_email(recipient:, subject:, content:, scheduled_at:, sender_name: nil)
|
|
9
|
+
payload = {
|
|
10
|
+
scheduled_email: {
|
|
11
|
+
recipient: recipient,
|
|
12
|
+
subject: subject,
|
|
13
|
+
content: content,
|
|
14
|
+
scheduled_at: scheduled_at,
|
|
15
|
+
sender_name: sender_name
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@client.post('/emails/schedule', body: payload.to_json)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Schedule bulk emails
|
|
23
|
+
def schedule_bulk_emails(subject:, content:, recipients:, scheduled_at:, sender_name: nil)
|
|
24
|
+
payload = {
|
|
25
|
+
bulk_scheduled_email: {
|
|
26
|
+
subject: subject,
|
|
27
|
+
content: content,
|
|
28
|
+
recipients: recipients,
|
|
29
|
+
scheduled_at: scheduled_at,
|
|
30
|
+
sender_name: sender_name
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@client.post('/emails/schedule-bulk', body: payload.to_json)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Schedule multiple individual emails
|
|
38
|
+
def schedule_multiple_emails(emails:)
|
|
39
|
+
payload = {
|
|
40
|
+
emails: emails
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@client.post('/emails/schedule-bulk-individual', body: payload.to_json)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Get scheduled emails
|
|
47
|
+
def scheduled_emails(params = {})
|
|
48
|
+
query_params = build_query_params(params)
|
|
49
|
+
@client.get("/emails/scheduled?#{query_params}")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Cancel scheduled email
|
|
53
|
+
def cancel_scheduled_email(id)
|
|
54
|
+
@client.delete("/emails/scheduled/#{id}")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def build_query_params(params)
|
|
60
|
+
params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Possinote
|
|
2
|
+
class SMS
|
|
3
|
+
def initialize(client)
|
|
4
|
+
@client = client
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
# Send a single SMS
|
|
8
|
+
def send(to:, message:, sender_id:)
|
|
9
|
+
payload = {
|
|
10
|
+
sms: {
|
|
11
|
+
to: to,
|
|
12
|
+
message: message,
|
|
13
|
+
sender_id: sender_id
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@client.post('/sms/send', body: payload.to_json)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Send bulk SMS
|
|
21
|
+
def send_bulk(sender_id:, messages:)
|
|
22
|
+
payload = {
|
|
23
|
+
bulk_sms: {
|
|
24
|
+
sender_id: sender_id,
|
|
25
|
+
messages: messages
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@client.post('/sms/bulk', body: payload.to_json)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Schedule a single SMS
|
|
33
|
+
def schedule(recipient:, message:, sender_id:, scheduled_at:)
|
|
34
|
+
payload = {
|
|
35
|
+
scheduled_sms: {
|
|
36
|
+
recipient: recipient,
|
|
37
|
+
message: message,
|
|
38
|
+
sender_id: sender_id,
|
|
39
|
+
scheduled_at: scheduled_at
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@client.post('/sms/schedule', body: payload.to_json)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Schedule bulk SMS
|
|
47
|
+
def schedule_bulk(sender_id:, messages:, scheduled_at:)
|
|
48
|
+
payload = {
|
|
49
|
+
bulk_scheduled_sms: {
|
|
50
|
+
sender_id: sender_id,
|
|
51
|
+
messages: messages,
|
|
52
|
+
scheduled_at: scheduled_at
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@client.post('/sms/schedule-bulk', body: payload.to_json)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get SMS history
|
|
60
|
+
def history(params = {})
|
|
61
|
+
query_params = build_query_params(params)
|
|
62
|
+
@client.get("/sms/history?#{query_params}")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Get scheduled SMS
|
|
66
|
+
def scheduled(params = {})
|
|
67
|
+
query_params = build_query_params(params)
|
|
68
|
+
@client.get("/sms/scheduled?#{query_params}")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Cancel scheduled SMS
|
|
72
|
+
def cancel_scheduled(id)
|
|
73
|
+
@client.delete("/sms/scheduled/#{id}")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def build_query_params(params)
|
|
79
|
+
params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join('&')
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
data/lib/possinote.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'httparty'
|
|
2
|
+
require 'json'
|
|
3
|
+
require_relative 'possinote/client'
|
|
4
|
+
require_relative 'possinote/sms'
|
|
5
|
+
require_relative 'possinote/email'
|
|
6
|
+
require_relative 'possinote/scheduling'
|
|
7
|
+
require_relative 'possinote/errors'
|
|
8
|
+
|
|
9
|
+
module Possinote
|
|
10
|
+
VERSION = '1.0.0'
|
|
11
|
+
|
|
12
|
+
class Error < StandardError; end
|
|
13
|
+
class AuthenticationError < Error; end
|
|
14
|
+
class PaymentRequiredError < Error; end
|
|
15
|
+
class RateLimitError < Error; end
|
|
16
|
+
class ValidationError < Error; end
|
|
17
|
+
class APIError < Error; end
|
|
18
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: possinote
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- PossiNote Team
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-08-11 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: httparty
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.21'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.21'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: json
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.6'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.6'
|
|
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.12'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '3.12'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: webmock
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.18'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.18'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rake
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '13.0'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '13.0'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: rubocop
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '1.50'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '1.50'
|
|
97
|
+
description: A comprehensive Ruby SDK for sending SMS, emails, and scheduling messages
|
|
98
|
+
via the PossiNote API
|
|
99
|
+
email:
|
|
100
|
+
- support@possitech.net
|
|
101
|
+
executables: []
|
|
102
|
+
extensions: []
|
|
103
|
+
extra_rdoc_files: []
|
|
104
|
+
files:
|
|
105
|
+
- LICENSE
|
|
106
|
+
- README.md
|
|
107
|
+
- lib/possinote.rb
|
|
108
|
+
- lib/possinote/client.rb
|
|
109
|
+
- lib/possinote/email.rb
|
|
110
|
+
- lib/possinote/errors.rb
|
|
111
|
+
- lib/possinote/scheduling.rb
|
|
112
|
+
- lib/possinote/sms.rb
|
|
113
|
+
homepage: https://github.com/charlesagyemang/POSSINOTE-RUBY-GEM
|
|
114
|
+
licenses:
|
|
115
|
+
- MIT
|
|
116
|
+
metadata:
|
|
117
|
+
source_code_uri: https://github.com/charlesagyemang/POSSINOTE-RUBY-GEM
|
|
118
|
+
changelog_uri: https://github.com/charlesagyemang/POSSINOTE-RUBY-GEM/blob/main/CHANGELOG.md
|
|
119
|
+
bug_tracker_uri: https://github.com/charlesagyemang/POSSINOTE-RUBY-GEM/issues
|
|
120
|
+
documentation_uri: https://github.com/charlesagyemang/POSSINOTE-RUBY-GEM
|
|
121
|
+
post_install_message:
|
|
122
|
+
rdoc_options: []
|
|
123
|
+
require_paths:
|
|
124
|
+
- lib
|
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
126
|
+
requirements:
|
|
127
|
+
- - ">="
|
|
128
|
+
- !ruby/object:Gem::Version
|
|
129
|
+
version: 2.6.0
|
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
131
|
+
requirements:
|
|
132
|
+
- - ">="
|
|
133
|
+
- !ruby/object:Gem::Version
|
|
134
|
+
version: '0'
|
|
135
|
+
requirements: []
|
|
136
|
+
rubygems_version: 3.3.3
|
|
137
|
+
signing_key:
|
|
138
|
+
specification_version: 4
|
|
139
|
+
summary: Official Ruby SDK for PossiNote API
|
|
140
|
+
test_files: []
|