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 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: []