unitpost 0.2.1 → 0.4.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 +4 -4
- data/README.md +47 -10
- data/lib/unitpost/client.rb +6 -2
- data/lib/unitpost/generated/operations.rb +508 -18
- data/lib/unitpost/generated/sms_models.rb +222 -0
- data/lib/unitpost/resources.rb +234 -2
- data/lib/unitpost/version.rb +1 -1
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9e9a3528c250381235631c5bb39f42374542bcccd093cdfda7fe1ea53a6f8173
|
|
4
|
+
data.tar.gz: b59b18ef0a50321e22ede9e3e090e1d4e92389f7abd38a1de37cd47797c5723b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9a3479a052e24411e80050aa8ca471ba06d61f26d2620ffe789c6257a039f6698fd17e2a43bb62adfd63802ab2c7691f2f99bed5301d8191ed99e59b2a2d7aaf
|
|
7
|
+
data.tar.gz: 822df4364aef7e1ffc2aec4bbdda76f6e07645ddcd6092b6e09f196a636a127e1b46ae4b81c317429a792c4d68ff90c4a4e4282162a5d956cca3239510f2a9ab
|
data/README.md
CHANGED
|
@@ -51,15 +51,16 @@ key at [unitpost.com → Settings → API keys](https://www.unitpost.com).
|
|
|
51
51
|
|
|
52
52
|
| Accessor | Covers |
|
|
53
53
|
| --- | --- |
|
|
54
|
-
| `
|
|
54
|
+
| `email` | Send, batch, list, get, update, stats, inbound (`received_*`) |
|
|
55
|
+
| `email.topics` | Topics + contact topic prefs |
|
|
56
|
+
| `email.campaigns` | Draft, schedule, send, pause/resume |
|
|
57
|
+
| `email.templates` | Template CRUD |
|
|
58
|
+
| `email.domains` | Domains + verify |
|
|
59
|
+
| `sms` | Send, get, list, `brands`, `numbers`, contact SMS consent. Available on paid plans: on the free plan `send` returns a `403` with code `limit_reached`, while the reads keep working. |
|
|
55
60
|
| `contacts` | CRUD, import jobs |
|
|
56
61
|
| `contact_fields` | Custom field definitions |
|
|
57
62
|
| `segments` | Segments + membership |
|
|
58
|
-
| `topics` | Topics + contact topic prefs |
|
|
59
|
-
| `campaigns` | Draft, schedule, send, pause/resume |
|
|
60
|
-
| `templates` | Template CRUD |
|
|
61
63
|
| `brand_kits` | Brand kit |
|
|
62
|
-
| `domains` | Domains + verify |
|
|
63
64
|
| `webhooks` | Endpoints + `verify` |
|
|
64
65
|
| `api_keys` | API key management |
|
|
65
66
|
| `suppressions` | Suppression list |
|
|
@@ -84,7 +85,7 @@ complaints are added automatically; you can also add your own:
|
|
|
84
85
|
unitpost.suppressions.create({ email: "bounced@example.com", reason: "manual" })
|
|
85
86
|
unitpost.suppressions.create({ emails: ["a@example.com", "b@example.com"] })
|
|
86
87
|
|
|
87
|
-
# Look one up by id or email; list
|
|
88
|
+
# Look one up by id or email; list your own; un-suppress.
|
|
88
89
|
unitpost.suppressions.get("bounced@example.com")
|
|
89
90
|
unitpost.suppressions.list_all.each { |s| puts "#{s["email"]} #{s["reason"]}" }
|
|
90
91
|
unitpost.suppressions.delete("bounced@example.com")
|
|
@@ -110,9 +111,44 @@ Or from the client (Resend-style alias):
|
|
|
110
111
|
event = client.webhooks.verify(payload: raw_body, secret: secret, headers: request.headers)
|
|
111
112
|
```
|
|
112
113
|
|
|
114
|
+
## Receive a text and answer it
|
|
115
|
+
|
|
116
|
+
US toll-free, 10DLC and Canadian numbers take replies (`two_way` is true on
|
|
117
|
+
`sms.numbers`). Subscribe a webhook to `sms.received`, skip STOP and texts
|
|
118
|
+
Unitpost already answered, read the conversation with `participant`, and answer
|
|
119
|
+
without `from` so the reply leaves from the number they texted.
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
require "unitpost"
|
|
123
|
+
|
|
124
|
+
unitpost = Unitpost::Client.new # reads UNITPOST_API_KEY
|
|
125
|
+
|
|
126
|
+
# Your webhook endpoint, subscribed to sms.received. Pass the RAW body.
|
|
127
|
+
def on_webhook(unitpost, raw_body, headers)
|
|
128
|
+
event = Unitpost.verify_webhook(
|
|
129
|
+
payload: raw_body,
|
|
130
|
+
secret: ENV.fetch("UNITPOST_WEBHOOK_SECRET"), # whsec_…
|
|
131
|
+
headers: headers
|
|
132
|
+
)
|
|
133
|
+
return unless event["type"] == "sms.received"
|
|
134
|
+
|
|
135
|
+
reply = event["data"]
|
|
136
|
+
return if reply["keyword"] == "OPT_OUT" || reply["auto_replied"] # STOP, or already answered
|
|
137
|
+
|
|
138
|
+
# The whole conversation with this person, both directions, newest first.
|
|
139
|
+
# Other filters: direction, status, from, to, contact_id, campaign_id,
|
|
140
|
+
# created_after, created_before.
|
|
141
|
+
history = unitpost.sms.list(participant: reply["from"], limit: 20)
|
|
142
|
+
answer = your_agent(history.data, reply["body"].to_s)
|
|
143
|
+
|
|
144
|
+
# No :from: the answer goes out from the number they texted.
|
|
145
|
+
unitpost.sms.send({ to: reply["from"], body: answer })
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
113
149
|
## Idempotency
|
|
114
150
|
|
|
115
|
-
`
|
|
151
|
+
`email.send` and `email.batch` are safe to retry by default — when you don't
|
|
116
152
|
pass `idempotency_key:`, the SDK generates one so an automatic retry can't
|
|
117
153
|
double-send. Pass your own to dedupe across separate calls:
|
|
118
154
|
|
|
@@ -132,7 +168,7 @@ Transient failures are retried automatically: `429`, `5xx`, and transport errors
|
|
|
132
168
|
`Retry-After` is honored; otherwise exponential backoff with full jitter is used.
|
|
133
169
|
Client errors (`401`/`403`/`404`/`409`/`422`) are never retried. Only requests
|
|
134
170
|
safe to replay are retried: `GET`s and writes carrying an idempotency key (which
|
|
135
|
-
`
|
|
171
|
+
`email.send` and `email.batch` always do); any other keyless write is never
|
|
136
172
|
auto-retried.
|
|
137
173
|
|
|
138
174
|
```ruby
|
|
@@ -145,7 +181,8 @@ unitpost = Unitpost::Client.new(max_retries: 2, retry_base_delay: 0.5, max_retry
|
|
|
145
181
|
| | |
|
|
146
182
|
| --- | --- |
|
|
147
183
|
| Docs + API reference | https://www.unitpost.com/docs |
|
|
148
|
-
| Product
|
|
184
|
+
| Product guides | https://www.unitpost.com/guides |
|
|
185
|
+
| SMTP | https://www.unitpost.com/docs/smtp |
|
|
149
186
|
| OpenAPI JSON | https://www.unitpost.com/api/v1/openapi.json |
|
|
150
187
|
| LLM index | https://www.unitpost.com/llms.txt |
|
|
151
188
|
| Dashboard / API keys | https://www.unitpost.com |
|
|
@@ -153,7 +190,7 @@ unitpost = Unitpost::Client.new(max_retries: 2, retry_base_delay: 0.5, max_retry
|
|
|
153
190
|
| Template gallery | https://www.unitpost.com/templates/gallery |
|
|
154
191
|
| Playground | https://www.unitpost.com/playground |
|
|
155
192
|
| Migration Assistant | https://www.unitpost.com/migration |
|
|
156
|
-
| MCP + Agent Skills | https://www.unitpost.com/
|
|
193
|
+
| MCP + Agent Skills | https://www.unitpost.com/docs/ai/mcp · https://www.unitpost.com/docs/ai/skills |
|
|
157
194
|
| Support | support@unitpost.com |
|
|
158
195
|
|
|
159
196
|
## License
|
data/lib/unitpost/client.rb
CHANGED
|
@@ -15,9 +15,9 @@ module Unitpost
|
|
|
15
15
|
# puts result.data["id"]
|
|
16
16
|
# end
|
|
17
17
|
class Client
|
|
18
|
-
attr_reader :email, :contacts, :contact_fields, :segments,
|
|
18
|
+
attr_reader :email, :sms, :contacts, :contact_fields, :segments,
|
|
19
19
|
:brand_kits, :webhooks, :api_keys, :suppressions,
|
|
20
|
-
:usage
|
|
20
|
+
:jobs, :usage
|
|
21
21
|
|
|
22
22
|
def initialize(api_key: nil, base_url: nil, timeout: 30, headers: {}, adapter: nil, stubs: nil,
|
|
23
23
|
max_retries: 2, retry_base_delay: 0.5, max_retry_delay: 20.0, sleeper: nil)
|
|
@@ -34,6 +34,9 @@ module Unitpost
|
|
|
34
34
|
sleeper: sleeper
|
|
35
35
|
)
|
|
36
36
|
@email = Resources::Email.new(http)
|
|
37
|
+
# SMS channel. Available on paid plans: on the free plan `send` returns
|
|
38
|
+
# a 403 with code `limit_reached`; reads keep working.
|
|
39
|
+
@sms = Resources::Sms.new(http)
|
|
37
40
|
@contacts = Resources::Contacts.new(http)
|
|
38
41
|
@contact_fields = Resources::ContactFields.new(http)
|
|
39
42
|
@segments = Resources::Segments.new(http)
|
|
@@ -43,6 +46,7 @@ module Unitpost
|
|
|
43
46
|
@suppressions = Resources::Suppressions.new(http)
|
|
44
47
|
# NOTE (pre-launch): `events` is intentionally absent while the
|
|
45
48
|
# custom-events surface is behind the launch gate.
|
|
49
|
+
@jobs = Resources::Jobs.new(http)
|
|
46
50
|
@usage = Resources::Usage.new(http)
|
|
47
51
|
end
|
|
48
52
|
end
|
|
@@ -104,7 +104,7 @@ module Unitpost
|
|
|
104
104
|
query_params: [],
|
|
105
105
|
has_body: false,
|
|
106
106
|
request_schema: nil,
|
|
107
|
-
response_schema:
|
|
107
|
+
response_schema: "Job",
|
|
108
108
|
is_list: false,
|
|
109
109
|
list_item_schema: nil
|
|
110
110
|
},
|
|
@@ -164,6 +164,20 @@ module Unitpost
|
|
|
164
164
|
is_list: false,
|
|
165
165
|
list_item_schema: nil
|
|
166
166
|
},
|
|
167
|
+
{
|
|
168
|
+
operation_id: "contacts.cancelImport",
|
|
169
|
+
resource: "contacts",
|
|
170
|
+
method_name: "cancelImport",
|
|
171
|
+
http_method: "POST",
|
|
172
|
+
path: "/contacts/imports/{id}/cancel",
|
|
173
|
+
path_params: ["id"],
|
|
174
|
+
query_params: [],
|
|
175
|
+
has_body: false,
|
|
176
|
+
request_schema: nil,
|
|
177
|
+
response_schema: "ContactImport",
|
|
178
|
+
is_list: false,
|
|
179
|
+
list_item_schema: nil
|
|
180
|
+
},
|
|
167
181
|
{
|
|
168
182
|
operation_id: "contacts.create",
|
|
169
183
|
resource: "contacts",
|
|
@@ -410,8 +424,8 @@ module Unitpost
|
|
|
410
424
|
path: "/email/campaigns/{id}/send",
|
|
411
425
|
path_params: ["id"],
|
|
412
426
|
query_params: [],
|
|
413
|
-
has_body:
|
|
414
|
-
request_schema:
|
|
427
|
+
has_body: true,
|
|
428
|
+
request_schema: "CampaignSend",
|
|
415
429
|
response_schema: "Campaign",
|
|
416
430
|
is_list: false,
|
|
417
431
|
list_item_schema: nil
|
|
@@ -486,6 +500,20 @@ module Unitpost
|
|
|
486
500
|
is_list: false,
|
|
487
501
|
list_item_schema: nil
|
|
488
502
|
},
|
|
503
|
+
{
|
|
504
|
+
operation_id: "email.domains.diagnose",
|
|
505
|
+
resource: "email.domains",
|
|
506
|
+
method_name: "diagnose",
|
|
507
|
+
http_method: "GET",
|
|
508
|
+
path: "/email/domains/{id}/diagnose",
|
|
509
|
+
path_params: ["id"],
|
|
510
|
+
query_params: [],
|
|
511
|
+
has_body: false,
|
|
512
|
+
request_schema: nil,
|
|
513
|
+
response_schema: "DomainDiagnosis",
|
|
514
|
+
is_list: false,
|
|
515
|
+
list_item_schema: nil
|
|
516
|
+
},
|
|
489
517
|
{
|
|
490
518
|
operation_id: "email.domains.get",
|
|
491
519
|
resource: "email.domains",
|
|
@@ -710,6 +738,20 @@ module Unitpost
|
|
|
710
738
|
is_list: true,
|
|
711
739
|
list_item_schema: "Template"
|
|
712
740
|
},
|
|
741
|
+
{
|
|
742
|
+
operation_id: "email.templates.testSend",
|
|
743
|
+
resource: "email.templates",
|
|
744
|
+
method_name: "testSend",
|
|
745
|
+
http_method: "POST",
|
|
746
|
+
path: "/email/templates/{id}/test-send",
|
|
747
|
+
path_params: ["id"],
|
|
748
|
+
query_params: [],
|
|
749
|
+
has_body: true,
|
|
750
|
+
request_schema: "TemplateTestSend",
|
|
751
|
+
response_schema: "TemplateTestSendResult",
|
|
752
|
+
is_list: false,
|
|
753
|
+
list_item_schema: nil
|
|
754
|
+
},
|
|
713
755
|
{
|
|
714
756
|
operation_id: "email.templates.update",
|
|
715
757
|
resource: "email.templates",
|
|
@@ -773,7 +815,7 @@ module Unitpost
|
|
|
773
815
|
http_method: "GET",
|
|
774
816
|
path: "/email/topics",
|
|
775
817
|
path_params: [],
|
|
776
|
-
query_params: ["limit", "after", "before"],
|
|
818
|
+
query_params: ["limit", "after", "before", "archived"],
|
|
777
819
|
has_body: false,
|
|
778
820
|
request_schema: nil,
|
|
779
821
|
response_schema: nil,
|
|
@@ -808,20 +850,6 @@ module Unitpost
|
|
|
808
850
|
is_list: false,
|
|
809
851
|
list_item_schema: nil
|
|
810
852
|
},
|
|
811
|
-
{
|
|
812
|
-
operation_id: "email.topics.setTopic",
|
|
813
|
-
resource: "email.topics",
|
|
814
|
-
method_name: "setTopic",
|
|
815
|
-
http_method: "PUT",
|
|
816
|
-
path: "/contacts/{id}/topics",
|
|
817
|
-
path_params: ["id"],
|
|
818
|
-
query_params: [],
|
|
819
|
-
has_body: true,
|
|
820
|
-
request_schema: "ContactTopicSet",
|
|
821
|
-
response_schema: "ContactTopic",
|
|
822
|
-
is_list: false,
|
|
823
|
-
list_item_schema: nil
|
|
824
|
-
},
|
|
825
853
|
{
|
|
826
854
|
operation_id: "email.topics.update",
|
|
827
855
|
resource: "email.topics",
|
|
@@ -850,6 +878,20 @@ module Unitpost
|
|
|
850
878
|
is_list: false,
|
|
851
879
|
list_item_schema: nil
|
|
852
880
|
},
|
|
881
|
+
{
|
|
882
|
+
operation_id: "jobs.get",
|
|
883
|
+
resource: "jobs",
|
|
884
|
+
method_name: "get",
|
|
885
|
+
http_method: "GET",
|
|
886
|
+
path: "/jobs/{id}",
|
|
887
|
+
path_params: ["id"],
|
|
888
|
+
query_params: [],
|
|
889
|
+
has_body: false,
|
|
890
|
+
request_schema: nil,
|
|
891
|
+
response_schema: "Job",
|
|
892
|
+
is_list: false,
|
|
893
|
+
list_item_schema: nil
|
|
894
|
+
},
|
|
853
895
|
{
|
|
854
896
|
operation_id: "segments.addMember",
|
|
855
897
|
resource: "segments",
|
|
@@ -962,6 +1004,426 @@ module Unitpost
|
|
|
962
1004
|
is_list: false,
|
|
963
1005
|
list_item_schema: nil
|
|
964
1006
|
},
|
|
1007
|
+
{
|
|
1008
|
+
operation_id: "sms.brands",
|
|
1009
|
+
resource: "sms",
|
|
1010
|
+
method_name: "brands",
|
|
1011
|
+
http_method: "GET",
|
|
1012
|
+
path: "/sms/brands",
|
|
1013
|
+
path_params: [],
|
|
1014
|
+
query_params: ["limit", "after", "before"],
|
|
1015
|
+
has_body: false,
|
|
1016
|
+
request_schema: nil,
|
|
1017
|
+
response_schema: nil,
|
|
1018
|
+
is_list: true,
|
|
1019
|
+
list_item_schema: "SmsBrand"
|
|
1020
|
+
},
|
|
1021
|
+
{
|
|
1022
|
+
operation_id: "sms.campaigns.cancel",
|
|
1023
|
+
resource: "sms.campaigns",
|
|
1024
|
+
method_name: "cancel",
|
|
1025
|
+
http_method: "POST",
|
|
1026
|
+
path: "/sms/campaigns/{id}/cancel",
|
|
1027
|
+
path_params: ["id"],
|
|
1028
|
+
query_params: [],
|
|
1029
|
+
has_body: false,
|
|
1030
|
+
request_schema: nil,
|
|
1031
|
+
response_schema: "SmsCampaign",
|
|
1032
|
+
is_list: false,
|
|
1033
|
+
list_item_schema: nil
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
operation_id: "sms.campaigns.create",
|
|
1037
|
+
resource: "sms.campaigns",
|
|
1038
|
+
method_name: "create",
|
|
1039
|
+
http_method: "POST",
|
|
1040
|
+
path: "/sms/campaigns",
|
|
1041
|
+
path_params: [],
|
|
1042
|
+
query_params: [],
|
|
1043
|
+
has_body: true,
|
|
1044
|
+
request_schema: "SmsCampaignCreate",
|
|
1045
|
+
response_schema: "SmsCampaign",
|
|
1046
|
+
is_list: false,
|
|
1047
|
+
list_item_schema: nil
|
|
1048
|
+
},
|
|
1049
|
+
{
|
|
1050
|
+
operation_id: "sms.campaigns.delete",
|
|
1051
|
+
resource: "sms.campaigns",
|
|
1052
|
+
method_name: "delete",
|
|
1053
|
+
http_method: "DELETE",
|
|
1054
|
+
path: "/sms/campaigns/{id}",
|
|
1055
|
+
path_params: ["id"],
|
|
1056
|
+
query_params: [],
|
|
1057
|
+
has_body: false,
|
|
1058
|
+
request_schema: nil,
|
|
1059
|
+
response_schema: nil,
|
|
1060
|
+
is_list: false,
|
|
1061
|
+
list_item_schema: nil
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
operation_id: "sms.campaigns.get",
|
|
1065
|
+
resource: "sms.campaigns",
|
|
1066
|
+
method_name: "get",
|
|
1067
|
+
http_method: "GET",
|
|
1068
|
+
path: "/sms/campaigns/{id}",
|
|
1069
|
+
path_params: ["id"],
|
|
1070
|
+
query_params: [],
|
|
1071
|
+
has_body: false,
|
|
1072
|
+
request_schema: nil,
|
|
1073
|
+
response_schema: "SmsCampaign",
|
|
1074
|
+
is_list: false,
|
|
1075
|
+
list_item_schema: nil
|
|
1076
|
+
},
|
|
1077
|
+
{
|
|
1078
|
+
operation_id: "sms.campaigns.list",
|
|
1079
|
+
resource: "sms.campaigns",
|
|
1080
|
+
method_name: "list",
|
|
1081
|
+
http_method: "GET",
|
|
1082
|
+
path: "/sms/campaigns",
|
|
1083
|
+
path_params: [],
|
|
1084
|
+
query_params: ["limit", "after", "before", "archived"],
|
|
1085
|
+
has_body: false,
|
|
1086
|
+
request_schema: nil,
|
|
1087
|
+
response_schema: nil,
|
|
1088
|
+
is_list: true,
|
|
1089
|
+
list_item_schema: "SmsCampaign"
|
|
1090
|
+
},
|
|
1091
|
+
{
|
|
1092
|
+
operation_id: "sms.campaigns.pause",
|
|
1093
|
+
resource: "sms.campaigns",
|
|
1094
|
+
method_name: "pause",
|
|
1095
|
+
http_method: "POST",
|
|
1096
|
+
path: "/sms/campaigns/{id}/pause",
|
|
1097
|
+
path_params: ["id"],
|
|
1098
|
+
query_params: [],
|
|
1099
|
+
has_body: false,
|
|
1100
|
+
request_schema: nil,
|
|
1101
|
+
response_schema: "SmsCampaign",
|
|
1102
|
+
is_list: false,
|
|
1103
|
+
list_item_schema: nil
|
|
1104
|
+
},
|
|
1105
|
+
{
|
|
1106
|
+
operation_id: "sms.campaigns.reschedule",
|
|
1107
|
+
resource: "sms.campaigns",
|
|
1108
|
+
method_name: "reschedule",
|
|
1109
|
+
http_method: "POST",
|
|
1110
|
+
path: "/sms/campaigns/{id}/reschedule",
|
|
1111
|
+
path_params: ["id"],
|
|
1112
|
+
query_params: [],
|
|
1113
|
+
has_body: true,
|
|
1114
|
+
request_schema: "SmsCampaignReschedule",
|
|
1115
|
+
response_schema: "SmsCampaign",
|
|
1116
|
+
is_list: false,
|
|
1117
|
+
list_item_schema: nil
|
|
1118
|
+
},
|
|
1119
|
+
{
|
|
1120
|
+
operation_id: "sms.campaigns.resume",
|
|
1121
|
+
resource: "sms.campaigns",
|
|
1122
|
+
method_name: "resume",
|
|
1123
|
+
http_method: "POST",
|
|
1124
|
+
path: "/sms/campaigns/{id}/resume",
|
|
1125
|
+
path_params: ["id"],
|
|
1126
|
+
query_params: [],
|
|
1127
|
+
has_body: false,
|
|
1128
|
+
request_schema: nil,
|
|
1129
|
+
response_schema: "SmsCampaign",
|
|
1130
|
+
is_list: false,
|
|
1131
|
+
list_item_schema: nil
|
|
1132
|
+
},
|
|
1133
|
+
{
|
|
1134
|
+
operation_id: "sms.campaigns.send",
|
|
1135
|
+
resource: "sms.campaigns",
|
|
1136
|
+
method_name: "send",
|
|
1137
|
+
http_method: "POST",
|
|
1138
|
+
path: "/sms/campaigns/{id}/send",
|
|
1139
|
+
path_params: ["id"],
|
|
1140
|
+
query_params: [],
|
|
1141
|
+
has_body: true,
|
|
1142
|
+
request_schema: "SmsCampaignSend",
|
|
1143
|
+
response_schema: "SmsCampaign",
|
|
1144
|
+
is_list: false,
|
|
1145
|
+
list_item_schema: nil
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
operation_id: "sms.campaigns.update",
|
|
1149
|
+
resource: "sms.campaigns",
|
|
1150
|
+
method_name: "update",
|
|
1151
|
+
http_method: "PATCH",
|
|
1152
|
+
path: "/sms/campaigns/{id}",
|
|
1153
|
+
path_params: ["id"],
|
|
1154
|
+
query_params: [],
|
|
1155
|
+
has_body: true,
|
|
1156
|
+
request_schema: "SmsCampaignUpdate",
|
|
1157
|
+
response_schema: "SmsCampaign",
|
|
1158
|
+
is_list: false,
|
|
1159
|
+
list_item_schema: nil
|
|
1160
|
+
},
|
|
1161
|
+
{
|
|
1162
|
+
operation_id: "sms.campaigns.validate",
|
|
1163
|
+
resource: "sms.campaigns",
|
|
1164
|
+
method_name: "validate",
|
|
1165
|
+
http_method: "GET",
|
|
1166
|
+
path: "/sms/campaigns/{id}/validate",
|
|
1167
|
+
path_params: ["id"],
|
|
1168
|
+
query_params: [],
|
|
1169
|
+
has_body: false,
|
|
1170
|
+
request_schema: nil,
|
|
1171
|
+
response_schema: "SmsCampaignValidation",
|
|
1172
|
+
is_list: false,
|
|
1173
|
+
list_item_schema: nil
|
|
1174
|
+
},
|
|
1175
|
+
{
|
|
1176
|
+
operation_id: "sms.get",
|
|
1177
|
+
resource: "sms",
|
|
1178
|
+
method_name: "get",
|
|
1179
|
+
http_method: "GET",
|
|
1180
|
+
path: "/sms/{id}",
|
|
1181
|
+
path_params: ["id"],
|
|
1182
|
+
query_params: [],
|
|
1183
|
+
has_body: false,
|
|
1184
|
+
request_schema: nil,
|
|
1185
|
+
response_schema: "Sms",
|
|
1186
|
+
is_list: false,
|
|
1187
|
+
list_item_schema: nil
|
|
1188
|
+
},
|
|
1189
|
+
{
|
|
1190
|
+
operation_id: "sms.list",
|
|
1191
|
+
resource: "sms",
|
|
1192
|
+
method_name: "list",
|
|
1193
|
+
http_method: "GET",
|
|
1194
|
+
path: "/sms",
|
|
1195
|
+
path_params: [],
|
|
1196
|
+
query_params: ["limit", "after", "before", "direction", "status", "from", "to", "participant", "contact_id", "campaign_id", "created_after", "created_before"],
|
|
1197
|
+
has_body: false,
|
|
1198
|
+
request_schema: nil,
|
|
1199
|
+
response_schema: nil,
|
|
1200
|
+
is_list: true,
|
|
1201
|
+
list_item_schema: "Sms"
|
|
1202
|
+
},
|
|
1203
|
+
{
|
|
1204
|
+
operation_id: "sms.numbers",
|
|
1205
|
+
resource: "sms",
|
|
1206
|
+
method_name: "numbers",
|
|
1207
|
+
http_method: "GET",
|
|
1208
|
+
path: "/sms/numbers",
|
|
1209
|
+
path_params: [],
|
|
1210
|
+
query_params: ["limit", "after", "before"],
|
|
1211
|
+
has_body: false,
|
|
1212
|
+
request_schema: nil,
|
|
1213
|
+
response_schema: nil,
|
|
1214
|
+
is_list: true,
|
|
1215
|
+
list_item_schema: "SmsNumber"
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
operation_id: "sms.recordSmsConsent",
|
|
1219
|
+
resource: "sms",
|
|
1220
|
+
method_name: "recordSmsConsent",
|
|
1221
|
+
http_method: "POST",
|
|
1222
|
+
path: "/contacts/{id}/sms-consent",
|
|
1223
|
+
path_params: ["id"],
|
|
1224
|
+
query_params: [],
|
|
1225
|
+
has_body: true,
|
|
1226
|
+
request_schema: "SmsConsentRecord",
|
|
1227
|
+
response_schema: "SmsConsentRecorded",
|
|
1228
|
+
is_list: false,
|
|
1229
|
+
list_item_schema: nil
|
|
1230
|
+
},
|
|
1231
|
+
{
|
|
1232
|
+
operation_id: "sms.send",
|
|
1233
|
+
resource: "sms",
|
|
1234
|
+
method_name: "send",
|
|
1235
|
+
http_method: "POST",
|
|
1236
|
+
path: "/sms",
|
|
1237
|
+
path_params: [],
|
|
1238
|
+
query_params: [],
|
|
1239
|
+
has_body: true,
|
|
1240
|
+
request_schema: "SmsSend",
|
|
1241
|
+
response_schema: "Sms",
|
|
1242
|
+
is_list: false,
|
|
1243
|
+
list_item_schema: nil
|
|
1244
|
+
},
|
|
1245
|
+
{
|
|
1246
|
+
operation_id: "sms.smsConsent",
|
|
1247
|
+
resource: "sms",
|
|
1248
|
+
method_name: "smsConsent",
|
|
1249
|
+
http_method: "GET",
|
|
1250
|
+
path: "/contacts/{id}/sms-consent",
|
|
1251
|
+
path_params: ["id"],
|
|
1252
|
+
query_params: [],
|
|
1253
|
+
has_body: false,
|
|
1254
|
+
request_schema: nil,
|
|
1255
|
+
response_schema: "SmsConsent",
|
|
1256
|
+
is_list: false,
|
|
1257
|
+
list_item_schema: nil
|
|
1258
|
+
},
|
|
1259
|
+
{
|
|
1260
|
+
operation_id: "sms.stats",
|
|
1261
|
+
resource: "sms",
|
|
1262
|
+
method_name: "stats",
|
|
1263
|
+
http_method: "GET",
|
|
1264
|
+
path: "/sms/stats",
|
|
1265
|
+
path_params: [],
|
|
1266
|
+
query_params: ["days"],
|
|
1267
|
+
has_body: false,
|
|
1268
|
+
request_schema: nil,
|
|
1269
|
+
response_schema: "SmsStats",
|
|
1270
|
+
is_list: false,
|
|
1271
|
+
list_item_schema: nil
|
|
1272
|
+
},
|
|
1273
|
+
{
|
|
1274
|
+
operation_id: "sms.templates.create",
|
|
1275
|
+
resource: "sms.templates",
|
|
1276
|
+
method_name: "create",
|
|
1277
|
+
http_method: "POST",
|
|
1278
|
+
path: "/sms/templates",
|
|
1279
|
+
path_params: [],
|
|
1280
|
+
query_params: [],
|
|
1281
|
+
has_body: true,
|
|
1282
|
+
request_schema: "SmsTemplateCreate",
|
|
1283
|
+
response_schema: "SmsTemplate",
|
|
1284
|
+
is_list: false,
|
|
1285
|
+
list_item_schema: nil
|
|
1286
|
+
},
|
|
1287
|
+
{
|
|
1288
|
+
operation_id: "sms.templates.delete",
|
|
1289
|
+
resource: "sms.templates",
|
|
1290
|
+
method_name: "delete",
|
|
1291
|
+
http_method: "DELETE",
|
|
1292
|
+
path: "/sms/templates/{id}",
|
|
1293
|
+
path_params: ["id"],
|
|
1294
|
+
query_params: [],
|
|
1295
|
+
has_body: false,
|
|
1296
|
+
request_schema: nil,
|
|
1297
|
+
response_schema: nil,
|
|
1298
|
+
is_list: false,
|
|
1299
|
+
list_item_schema: nil
|
|
1300
|
+
},
|
|
1301
|
+
{
|
|
1302
|
+
operation_id: "sms.templates.get",
|
|
1303
|
+
resource: "sms.templates",
|
|
1304
|
+
method_name: "get",
|
|
1305
|
+
http_method: "GET",
|
|
1306
|
+
path: "/sms/templates/{id}",
|
|
1307
|
+
path_params: ["id"],
|
|
1308
|
+
query_params: [],
|
|
1309
|
+
has_body: false,
|
|
1310
|
+
request_schema: nil,
|
|
1311
|
+
response_schema: "SmsTemplate",
|
|
1312
|
+
is_list: false,
|
|
1313
|
+
list_item_schema: nil
|
|
1314
|
+
},
|
|
1315
|
+
{
|
|
1316
|
+
operation_id: "sms.templates.list",
|
|
1317
|
+
resource: "sms.templates",
|
|
1318
|
+
method_name: "list",
|
|
1319
|
+
http_method: "GET",
|
|
1320
|
+
path: "/sms/templates",
|
|
1321
|
+
path_params: [],
|
|
1322
|
+
query_params: ["limit", "after", "before"],
|
|
1323
|
+
has_body: false,
|
|
1324
|
+
request_schema: nil,
|
|
1325
|
+
response_schema: nil,
|
|
1326
|
+
is_list: true,
|
|
1327
|
+
list_item_schema: "SmsTemplate"
|
|
1328
|
+
},
|
|
1329
|
+
{
|
|
1330
|
+
operation_id: "sms.templates.update",
|
|
1331
|
+
resource: "sms.templates",
|
|
1332
|
+
method_name: "update",
|
|
1333
|
+
http_method: "PATCH",
|
|
1334
|
+
path: "/sms/templates/{id}",
|
|
1335
|
+
path_params: ["id"],
|
|
1336
|
+
query_params: [],
|
|
1337
|
+
has_body: true,
|
|
1338
|
+
request_schema: "SmsTemplateUpdate",
|
|
1339
|
+
response_schema: "SmsTemplate",
|
|
1340
|
+
is_list: false,
|
|
1341
|
+
list_item_schema: nil
|
|
1342
|
+
},
|
|
1343
|
+
{
|
|
1344
|
+
operation_id: "sms.topics.create",
|
|
1345
|
+
resource: "sms.topics",
|
|
1346
|
+
method_name: "create",
|
|
1347
|
+
http_method: "POST",
|
|
1348
|
+
path: "/sms/topics",
|
|
1349
|
+
path_params: [],
|
|
1350
|
+
query_params: [],
|
|
1351
|
+
has_body: true,
|
|
1352
|
+
request_schema: "TopicCreate",
|
|
1353
|
+
response_schema: "Topic",
|
|
1354
|
+
is_list: false,
|
|
1355
|
+
list_item_schema: nil
|
|
1356
|
+
},
|
|
1357
|
+
{
|
|
1358
|
+
operation_id: "sms.topics.delete",
|
|
1359
|
+
resource: "sms.topics",
|
|
1360
|
+
method_name: "delete",
|
|
1361
|
+
http_method: "DELETE",
|
|
1362
|
+
path: "/sms/topics/{id}",
|
|
1363
|
+
path_params: ["id"],
|
|
1364
|
+
query_params: [],
|
|
1365
|
+
has_body: false,
|
|
1366
|
+
request_schema: nil,
|
|
1367
|
+
response_schema: nil,
|
|
1368
|
+
is_list: false,
|
|
1369
|
+
list_item_schema: nil
|
|
1370
|
+
},
|
|
1371
|
+
{
|
|
1372
|
+
operation_id: "sms.topics.get",
|
|
1373
|
+
resource: "sms.topics",
|
|
1374
|
+
method_name: "get",
|
|
1375
|
+
http_method: "GET",
|
|
1376
|
+
path: "/sms/topics/{id}",
|
|
1377
|
+
path_params: ["id"],
|
|
1378
|
+
query_params: [],
|
|
1379
|
+
has_body: false,
|
|
1380
|
+
request_schema: nil,
|
|
1381
|
+
response_schema: "Topic",
|
|
1382
|
+
is_list: false,
|
|
1383
|
+
list_item_schema: nil
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
operation_id: "sms.topics.list",
|
|
1387
|
+
resource: "sms.topics",
|
|
1388
|
+
method_name: "list",
|
|
1389
|
+
http_method: "GET",
|
|
1390
|
+
path: "/sms/topics",
|
|
1391
|
+
path_params: [],
|
|
1392
|
+
query_params: ["limit", "after", "before", "archived"],
|
|
1393
|
+
has_body: false,
|
|
1394
|
+
request_schema: nil,
|
|
1395
|
+
response_schema: nil,
|
|
1396
|
+
is_list: true,
|
|
1397
|
+
list_item_schema: "Topic"
|
|
1398
|
+
},
|
|
1399
|
+
{
|
|
1400
|
+
operation_id: "sms.topics.update",
|
|
1401
|
+
resource: "sms.topics",
|
|
1402
|
+
method_name: "update",
|
|
1403
|
+
http_method: "PATCH",
|
|
1404
|
+
path: "/sms/topics/{id}",
|
|
1405
|
+
path_params: ["id"],
|
|
1406
|
+
query_params: [],
|
|
1407
|
+
has_body: true,
|
|
1408
|
+
request_schema: "TopicUpdate",
|
|
1409
|
+
response_schema: "Topic",
|
|
1410
|
+
is_list: false,
|
|
1411
|
+
list_item_schema: nil
|
|
1412
|
+
},
|
|
1413
|
+
{
|
|
1414
|
+
operation_id: "sms.update",
|
|
1415
|
+
resource: "sms",
|
|
1416
|
+
method_name: "update",
|
|
1417
|
+
http_method: "PATCH",
|
|
1418
|
+
path: "/sms/{id}",
|
|
1419
|
+
path_params: ["id"],
|
|
1420
|
+
query_params: [],
|
|
1421
|
+
has_body: true,
|
|
1422
|
+
request_schema: "SmsPatch",
|
|
1423
|
+
response_schema: "Sms",
|
|
1424
|
+
is_list: false,
|
|
1425
|
+
list_item_schema: nil
|
|
1426
|
+
},
|
|
965
1427
|
{
|
|
966
1428
|
operation_id: "suppressions.create",
|
|
967
1429
|
resource: "suppressions",
|
|
@@ -1088,6 +1550,34 @@ module Unitpost
|
|
|
1088
1550
|
is_list: true,
|
|
1089
1551
|
list_item_schema: "Webhook"
|
|
1090
1552
|
},
|
|
1553
|
+
{
|
|
1554
|
+
operation_id: "webhooks.listDeliveries",
|
|
1555
|
+
resource: "webhooks",
|
|
1556
|
+
method_name: "listDeliveries",
|
|
1557
|
+
http_method: "GET",
|
|
1558
|
+
path: "/webhooks/{id}/deliveries",
|
|
1559
|
+
path_params: ["id"],
|
|
1560
|
+
query_params: ["limit", "after", "before"],
|
|
1561
|
+
has_body: false,
|
|
1562
|
+
request_schema: nil,
|
|
1563
|
+
response_schema: nil,
|
|
1564
|
+
is_list: true,
|
|
1565
|
+
list_item_schema: "WebhookDelivery"
|
|
1566
|
+
},
|
|
1567
|
+
{
|
|
1568
|
+
operation_id: "webhooks.rotateSecret",
|
|
1569
|
+
resource: "webhooks",
|
|
1570
|
+
method_name: "rotateSecret",
|
|
1571
|
+
http_method: "POST",
|
|
1572
|
+
path: "/webhooks/{id}/rotate-secret",
|
|
1573
|
+
path_params: ["id"],
|
|
1574
|
+
query_params: [],
|
|
1575
|
+
has_body: false,
|
|
1576
|
+
request_schema: nil,
|
|
1577
|
+
response_schema: "WebhookSecretRotated",
|
|
1578
|
+
is_list: false,
|
|
1579
|
+
list_item_schema: nil
|
|
1580
|
+
},
|
|
1091
1581
|
{
|
|
1092
1582
|
operation_id: "webhooks.test",
|
|
1093
1583
|
resource: "webhooks",
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Code generated by @unitpost/sdk-codegen from the public OpenAPI spec.
|
|
2
|
+
# DO NOT EDIT BY HAND. Run `npm run sdk:generate` to regenerate.
|
|
3
|
+
# Source of truth: apps/web/docs/openapi.json (itself derived from the API's
|
|
4
|
+
# Zod schemas and CI-gated for drift).
|
|
5
|
+
|
|
6
|
+
# frozen_string_literal: true
|
|
7
|
+
|
|
8
|
+
module Unitpost
|
|
9
|
+
module Generated
|
|
10
|
+
# Why a message was skipped before reaching the carrier. `out_of_reach`: no
|
|
11
|
+
# sender covers the recipient's country; `unsupported_country`: no
|
|
12
|
+
# compliance rule for the country yet, or SMS to that country is paused for
|
|
13
|
+
# now (every message, transactional included, until it resumes); `no_phone`:
|
|
14
|
+
# the contact has no SMS handle; `opted_out`: STOP / suppression /
|
|
15
|
+
# unreachable; `no_marketing_consent`: marketing without express consent;
|
|
16
|
+
# `quiet_hours_deferred`: delayed to the recipient's local window;
|
|
17
|
+
# `sender_not_active`: the sender lost approval.
|
|
18
|
+
module SmsSkipReason
|
|
19
|
+
OUT_OF_REACH = "out_of_reach"
|
|
20
|
+
UNSUPPORTED_COUNTRY = "unsupported_country"
|
|
21
|
+
NO_PHONE = "no_phone"
|
|
22
|
+
OPTED_OUT = "opted_out"
|
|
23
|
+
NO_MARKETING_CONSENT = "no_marketing_consent"
|
|
24
|
+
QUIET_HOURS_DEFERRED = "quiet_hours_deferred"
|
|
25
|
+
SENDER_NOT_ACTIVE = "sender_not_active"
|
|
26
|
+
ALL = [OUT_OF_REACH, UNSUPPORTED_COUNTRY, NO_PHONE, OPTED_OUT, NO_MARKETING_CONSENT, QUIET_HOURS_DEFERRED, SENDER_NOT_ACTIVE].freeze
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The `SmsError` object from the public API.
|
|
30
|
+
# @!attribute code Why a message was skipped before reaching the carrier. `out_of_reach`: no sender covers the recipient's country; `unsupported_country`: no compliance rule for the country yet, or SMS to that country is paused for now (every message, transactional included, until it resumes); `no_phone`: the contact has no SMS handle; `opted_out`: STOP / suppression / unreachable; `no_marketing_consent`: marketing without express consent; `quiet_hours_deferred`: delayed to the recipient's local window; `sender_not_active`: the sender lost approval.
|
|
31
|
+
# @!attribute message Human-readable sentence for `code`.
|
|
32
|
+
SmsError = Struct.new(:code, :message, keyword_init: true) do
|
|
33
|
+
# @param h [Hash, nil] the decoded API object (string keys)
|
|
34
|
+
# @return [SmsError, nil]
|
|
35
|
+
def self.from_hash(h)
|
|
36
|
+
return nil if h.nil?
|
|
37
|
+
h = h.transform_keys(&:to_s)
|
|
38
|
+
new(
|
|
39
|
+
code: h["code"],
|
|
40
|
+
message: h["message"]
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# The `SmsNumberUseCase` object from the public API.
|
|
46
|
+
# @!attribute name
|
|
47
|
+
# @!attribute category
|
|
48
|
+
SmsNumberUseCase = Struct.new(:name, :category, keyword_init: true) do
|
|
49
|
+
# @param h [Hash, nil] the decoded API object (string keys)
|
|
50
|
+
# @return [SmsNumberUseCase, nil]
|
|
51
|
+
def self.from_hash(h)
|
|
52
|
+
return nil if h.nil?
|
|
53
|
+
h = h.transform_keys(&:to_s)
|
|
54
|
+
new(
|
|
55
|
+
name: h["name"],
|
|
56
|
+
category: h["category"]
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The `SmsNumberSenderIdGroupCountriesItem` object from the public API.
|
|
62
|
+
# @!attribute country ISO-3166 alpha-2.
|
|
63
|
+
# @!attribute status One of: pending, action_required, active, rejected, released.
|
|
64
|
+
SmsNumberSenderIdGroupCountriesItem = Struct.new(:country, :status, keyword_init: true) do
|
|
65
|
+
# @param h [Hash, nil] the decoded API object (string keys)
|
|
66
|
+
# @return [SmsNumberSenderIdGroupCountriesItem, nil]
|
|
67
|
+
def self.from_hash(h)
|
|
68
|
+
return nil if h.nil?
|
|
69
|
+
h = h.transform_keys(&:to_s)
|
|
70
|
+
new(
|
|
71
|
+
country: h["country"],
|
|
72
|
+
status: h["status"]
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The `SmsNumberSenderIdGroup` object from the public API.
|
|
78
|
+
# @!attribute name The Sender ID string recipients see.
|
|
79
|
+
# @!attribute brand_id The brand that owns this Sender ID.
|
|
80
|
+
# @!attribute countries Every country this Sender ID is enabled for, with each country's own status.
|
|
81
|
+
SmsNumberSenderIdGroup = Struct.new(:name, :brand_id, :countries, keyword_init: true) do
|
|
82
|
+
# @param h [Hash, nil] the decoded API object (string keys)
|
|
83
|
+
# @return [SmsNumberSenderIdGroup, nil]
|
|
84
|
+
def self.from_hash(h)
|
|
85
|
+
return nil if h.nil?
|
|
86
|
+
h = h.transform_keys(&:to_s)
|
|
87
|
+
new(
|
|
88
|
+
name: h["name"],
|
|
89
|
+
brand_id: h["brand_id"],
|
|
90
|
+
countries: (h["countries"] || []).map { |x| SmsNumberSenderIdGroupCountriesItem.from_hash(x) }
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# The `Sms` object from the public API.
|
|
96
|
+
# @!attribute object Always "sms".
|
|
97
|
+
# @!attribute id
|
|
98
|
+
# @!attribute to
|
|
99
|
+
# @!attribute from
|
|
100
|
+
# @!attribute direction One of: outbound, inbound.
|
|
101
|
+
# @!attribute status One of: queued, scheduled, sending, sent, delivered, failed, suppressed, canceled, received.
|
|
102
|
+
# @!attribute type One of: transactional, marketing.
|
|
103
|
+
# @!attribute body
|
|
104
|
+
# @!attribute segments Billed units. Outbound: segments. Inbound: 1 when the received text was charged (1¢ in the US and Canada), else 0.
|
|
105
|
+
# @!attribute in_reply_to Inbound only: your text this reply answers (`sms_…`), when the carrier says which. Null otherwise.
|
|
106
|
+
# @!attribute received_at Inbound only: when the reply reached Unitpost. Order a conversation by this; carriers don't guarantee order. Null on outbound.
|
|
107
|
+
# @!attribute looks_like_opt_out Inbound only: the text reads like a request to stop texting ("please stop texting me") but was not an opt-out keyword, so no opt-out was recorded. Decide whether to record one (POST /v1/contacts/{id}/sms-consent, action opt_out). False for an exact keyword such as STOP (already recorded) and on outbound.
|
|
108
|
+
# @!attribute country
|
|
109
|
+
# @!attribute scheduled_at
|
|
110
|
+
# @!attribute last_error Legacy free-text error. Prefer `error.code`.
|
|
111
|
+
# @!attribute error Set on `suppressed` messages: the stable skip reason and its sentence. Null otherwise.
|
|
112
|
+
# @!attribute created_at
|
|
113
|
+
Sms = Struct.new(:object, :id, :to, :from, :direction, :status, :type, :body, :segments, :in_reply_to, :received_at, :looks_like_opt_out, :country, :scheduled_at, :last_error, :error, :created_at, keyword_init: true) do
|
|
114
|
+
# @param h [Hash, nil] the decoded API object (string keys)
|
|
115
|
+
# @return [Sms, nil]
|
|
116
|
+
def self.from_hash(h)
|
|
117
|
+
return nil if h.nil?
|
|
118
|
+
h = h.transform_keys(&:to_s)
|
|
119
|
+
new(
|
|
120
|
+
object: h["object"],
|
|
121
|
+
id: h["id"],
|
|
122
|
+
to: h["to"],
|
|
123
|
+
from: h["from"],
|
|
124
|
+
direction: h["direction"],
|
|
125
|
+
status: h["status"],
|
|
126
|
+
type: h["type"],
|
|
127
|
+
body: h["body"],
|
|
128
|
+
segments: h["segments"],
|
|
129
|
+
in_reply_to: h["in_reply_to"],
|
|
130
|
+
received_at: h["received_at"],
|
|
131
|
+
looks_like_opt_out: h["looks_like_opt_out"],
|
|
132
|
+
country: h["country"],
|
|
133
|
+
scheduled_at: h["scheduled_at"],
|
|
134
|
+
last_error: h["last_error"],
|
|
135
|
+
error: SmsError.from_hash(h["error"]),
|
|
136
|
+
created_at: h["created_at"]
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# The `SmsBrand` object from the public API.
|
|
142
|
+
# @!attribute object Always "sms_brand".
|
|
143
|
+
# @!attribute id Prefixed brand id, e.g. `sbrand_…`.
|
|
144
|
+
# @!attribute name Public-facing brand name.
|
|
145
|
+
# @!attribute legal_name Registered legal entity name.
|
|
146
|
+
# @!attribute website Brand website URL.
|
|
147
|
+
# @!attribute domain Registrable domain derived from the website. The brand contact email must be on this domain.
|
|
148
|
+
# @!attribute contact_email
|
|
149
|
+
# @!attribute status `incomplete` and `action_required` need the customer to act in the dashboard; `pending` is waiting on the carrier. One of: incomplete, ready, pending, action_required, verified, rejected, archived.
|
|
150
|
+
# @!attribute missing Human-readable list of what is still required before this brand can back a number. Empty when nothing is missing.
|
|
151
|
+
# @!attribute ten_dlc_verified True when the 10DLC brand registration is approved.
|
|
152
|
+
# @!attribute tax_id_on_file
|
|
153
|
+
# @!attribute number_count Non-released senders on this brand (phone numbers and Sender ID countries, any status).
|
|
154
|
+
# @!attribute editable False once the brand is locked: carriers approved or are reviewing its 10DLC registration, or senders are registered with its details. Locked brands are corrected only when carriers ask.
|
|
155
|
+
# @!attribute created_at
|
|
156
|
+
SmsBrand = Struct.new(:object, :id, :name, :legal_name, :website, :domain, :contact_email, :status, :missing, :ten_dlc_verified, :tax_id_on_file, :number_count, :editable, :created_at, keyword_init: true) do
|
|
157
|
+
# @param h [Hash, nil] the decoded API object (string keys)
|
|
158
|
+
# @return [SmsBrand, nil]
|
|
159
|
+
def self.from_hash(h)
|
|
160
|
+
return nil if h.nil?
|
|
161
|
+
h = h.transform_keys(&:to_s)
|
|
162
|
+
new(
|
|
163
|
+
object: h["object"],
|
|
164
|
+
id: h["id"],
|
|
165
|
+
name: h["name"],
|
|
166
|
+
legal_name: h["legal_name"],
|
|
167
|
+
website: h["website"],
|
|
168
|
+
domain: h["domain"],
|
|
169
|
+
contact_email: h["contact_email"],
|
|
170
|
+
status: h["status"],
|
|
171
|
+
missing: h["missing"],
|
|
172
|
+
ten_dlc_verified: h["ten_dlc_verified"],
|
|
173
|
+
tax_id_on_file: h["tax_id_on_file"],
|
|
174
|
+
number_count: h["number_count"],
|
|
175
|
+
editable: h["editable"],
|
|
176
|
+
created_at: h["created_at"]
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# The `SmsNumber` object from the public API.
|
|
182
|
+
# @!attribute object Always "sms_number".
|
|
183
|
+
# @!attribute id Prefixed number id, e.g. `snum_…`.
|
|
184
|
+
# @!attribute phone_number E.164 number. Null while a 10DLC registration is still in review; the number is assigned on approval.
|
|
185
|
+
# @!attribute type `long_code` is a local number issued outside the US (today: Canada, `country: "CA"`, reach `["CA"]`; instant, no carrier registration). `sender_id` is an alphanumeric Sender ID (one entry per enabled country; see `sender_id_group`). `simulator` only delivers to verified test destinations; it is not a production sender. One of: toll_free, ten_dlc, long_code, sender_id, simulator.
|
|
186
|
+
# @!attribute status Only `active` numbers can send. One of: pending, action_required, active, rejected, released.
|
|
187
|
+
# @!attribute is_default Informational: the workspace's first or primary number. Sends route by the recipient's country, not by this flag.
|
|
188
|
+
# @!attribute country ISO-3166 alpha-2 country the number was issued in (`US` for toll-free and 10DLC, `CA` for a Canadian long code). Null until recorded.
|
|
189
|
+
# @!attribute two_way Whether people can text this number: once it is active, a text to it fires the `sms.received` webhook and costs 1¢. True for US toll-free and 10DLC numbers and Canadian long codes (two-way is offered in the US and Canada only); false for Sender IDs, which are one-way, and for the simulator.
|
|
190
|
+
# @!attribute reach ISO-3166 alpha-2 countries this number can deliver to. US toll-free and 10DLC numbers reach `["US"]` only; a Canadian long code reaches `["CA"]` only. A recipient outside this list is skipped before send (status `suppressed`, not billed) rather than forwarded best-effort from a shared identity. Empty while the country is unknown.
|
|
191
|
+
# @!attribute brand_id The brand this number is registered under.
|
|
192
|
+
# @!attribute use_case What carriers approved this number to send. Required for approval; submitted with the number.
|
|
193
|
+
# @!attribute action_required_reason Set when status is `action_required` (what to fix: carrier feedback, why a submission failed, or a request that never reached the registry) or `rejected`.
|
|
194
|
+
# @!attribute sender_id_group Present on `sender_id` entries only. A Sender ID is ONE identity per brand enabled per country: the list still returns one entry per (Sender ID, country) so existing integrations keep working, and every entry of the same identity carries the same `sender_id_group` describing the whole set.
|
|
195
|
+
# @!attribute created_at
|
|
196
|
+
SmsNumber = Struct.new(:object, :id, :phone_number, :type, :status, :is_default, :country, :two_way, :reach, :brand_id, :use_case, :action_required_reason, :sender_id_group, :created_at, keyword_init: true) do
|
|
197
|
+
# @param h [Hash, nil] the decoded API object (string keys)
|
|
198
|
+
# @return [SmsNumber, nil]
|
|
199
|
+
def self.from_hash(h)
|
|
200
|
+
return nil if h.nil?
|
|
201
|
+
h = h.transform_keys(&:to_s)
|
|
202
|
+
new(
|
|
203
|
+
object: h["object"],
|
|
204
|
+
id: h["id"],
|
|
205
|
+
phone_number: h["phone_number"],
|
|
206
|
+
type: h["type"],
|
|
207
|
+
status: h["status"],
|
|
208
|
+
is_default: h["is_default"],
|
|
209
|
+
country: h["country"],
|
|
210
|
+
two_way: h["two_way"],
|
|
211
|
+
reach: h["reach"],
|
|
212
|
+
brand_id: h["brand_id"],
|
|
213
|
+
use_case: SmsNumberUseCase.from_hash(h["use_case"]),
|
|
214
|
+
action_required_reason: h["action_required_reason"],
|
|
215
|
+
sender_id_group: SmsNumberSenderIdGroup.from_hash(h["sender_id_group"]),
|
|
216
|
+
created_at: h["created_at"]
|
|
217
|
+
)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
end
|
|
222
|
+
end
|
data/lib/unitpost/resources.rb
CHANGED
|
@@ -27,7 +27,14 @@ module Unitpost
|
|
|
27
27
|
{ limit: limit, after: after, before: before }.merge(extra)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
FORWARD_ONLY_MESSAGE = "list_all pages forward only: `before` is not supported (use list to page backward)."
|
|
31
|
+
|
|
32
|
+
# Forward only, refused up front: a `before` cursor used to ride along
|
|
33
|
+
# with the moving `after` and 422 on the second page.
|
|
30
34
|
def paginate(path, params)
|
|
35
|
+
before = params[:before] || params["before"]
|
|
36
|
+
raise ArgumentError, FORWARD_ONLY_MESSAGE if before && !before.to_s.empty?
|
|
37
|
+
|
|
31
38
|
Enumerator.new do |yielder|
|
|
32
39
|
after = params[:after]
|
|
33
40
|
loop do
|
|
@@ -46,6 +53,193 @@ module Unitpost
|
|
|
46
53
|
end
|
|
47
54
|
end
|
|
48
55
|
|
|
56
|
+
class Sms < Base
|
|
57
|
+
attr_reader :templates, :campaigns, :topics
|
|
58
|
+
|
|
59
|
+
def initialize(http)
|
|
60
|
+
super(http)
|
|
61
|
+
@templates = SmsTemplates.new(http)
|
|
62
|
+
@campaigns = SmsCampaigns.new(http)
|
|
63
|
+
@topics = SmsTopics.new(http)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# SMS channel (paid plans). Recipient must be E.164; marketing sends require
|
|
67
|
+
# prior express consent on record and honor recipient-local quiet hours.
|
|
68
|
+
# Billing is per segment. When no idempotency_key is given one is
|
|
69
|
+
# generated so a transient retry can't double-send.
|
|
70
|
+
def send(body, idempotency_key: nil)
|
|
71
|
+
@http.request("POST", "/sms", body: body, idempotency_key: idempotency_key || SecureRandom.uuid)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def get(id)
|
|
75
|
+
@http.request("GET", "/sms/#{enc(id)}")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Cancel a queued/scheduled SMS ({ cancel: true }, credit refunded) or
|
|
79
|
+
# move a scheduled one ({ scheduled_at: "<ISO 8601>" }).
|
|
80
|
+
def update(id, body)
|
|
81
|
+
@http.request("PATCH", "/sms/#{enc(id)}", body: body)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def list(params = {})
|
|
85
|
+
@http.request("GET", "/sms", query: params)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def list_all(params = {})
|
|
89
|
+
paginate("/sms", params)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# List SMS brands and their setup status.
|
|
93
|
+
#
|
|
94
|
+
# A brand is the business identity carriers register before they approve a
|
|
95
|
+
# phone number, so this is what to poll to answer "is SMS ready yet?":
|
|
96
|
+
# +status+ says whether you or the carrier is the blocker, and +missing+
|
|
97
|
+
# lists exactly what is still required.
|
|
98
|
+
#
|
|
99
|
+
# Read-only — creating a brand is a paid carrier submission and is only
|
|
100
|
+
# available in the dashboard.
|
|
101
|
+
def brands(params = {})
|
|
102
|
+
@http.request("GET", "/sms/brands", query: params)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# List SMS phone numbers.
|
|
106
|
+
#
|
|
107
|
+
# Only numbers with +status+ of "active" can send. A +ten_dlc+ number has
|
|
108
|
+
# no +phone_number+ until carriers approve it, and a +simulator+ number
|
|
109
|
+
# only reaches verified test destinations.
|
|
110
|
+
#
|
|
111
|
+
# Read-only — requesting or releasing a number is only available in the
|
|
112
|
+
# dashboard.
|
|
113
|
+
def numbers(params = {})
|
|
114
|
+
@http.request("GET", "/sms/numbers", query: params)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# SMS delivery stats over a rolling window (days: 1–365, defaults to 30
|
|
118
|
+
# server-side): queued, sent, delivered, failed, suppressed, canceled,
|
|
119
|
+
# inbound and segments billed, with delivery, failure and suppression
|
|
120
|
+
# rates. No opens or clicks on SMS.
|
|
121
|
+
def stats(days: nil)
|
|
122
|
+
@http.request("GET", "/sms/stats", query: { days: days })
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Read a contact's SMS consent state and immutable consent history.
|
|
126
|
+
def sms_consent(contact_id)
|
|
127
|
+
@http.request("GET", "/contacts/#{enc(contact_id)}/sms-consent")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Record an SMS consent change (opt-in or opt-out) for a contact.
|
|
131
|
+
def record_sms_consent(contact_id, body)
|
|
132
|
+
@http.request("POST", "/contacts/#{enc(contact_id)}/sms-consent", body: body)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# SMS templates: plain-text bodies with {{variable}} placeholders.
|
|
137
|
+
class SmsTemplates < Base
|
|
138
|
+
def list(limit: nil, after: nil, before: nil)
|
|
139
|
+
@http.request("GET", "/sms/templates", query: list_params(limit: limit, after: after, before: before))
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def list_all(**params)
|
|
143
|
+
paginate("/sms/templates", params)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def create(body)
|
|
147
|
+
@http.request("POST", "/sms/templates", body: body)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def get(id)
|
|
151
|
+
@http.request("GET", "/sms/templates/#{enc(id)}")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def update(id, body)
|
|
155
|
+
@http.request("PATCH", "/sms/templates/#{enc(id)}", body: body)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def delete(id)
|
|
159
|
+
@http.request("DELETE", "/sms/templates/#{enc(id)}")
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# SMS campaigns: a published SMS template sent to a segment. create makes a
|
|
164
|
+
# draft (omit sender_id for automatic routing); send sends now or at
|
|
165
|
+
# scheduled_at; delete archives.
|
|
166
|
+
class SmsCampaigns < Base
|
|
167
|
+
def list(limit: nil, after: nil, before: nil)
|
|
168
|
+
@http.request("GET", "/sms/campaigns", query: list_params(limit: limit, after: after, before: before))
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def list_all(**params)
|
|
172
|
+
paginate("/sms/campaigns", params)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def create(body)
|
|
176
|
+
@http.request("POST", "/sms/campaigns", body: body)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def get(id)
|
|
180
|
+
@http.request("GET", "/sms/campaigns/#{enc(id)}")
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def update(id, body)
|
|
184
|
+
@http.request("PATCH", "/sms/campaigns/#{enc(id)}", body: body)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def delete(id)
|
|
188
|
+
@http.request("DELETE", "/sms/campaigns/#{enc(id)}")
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def send(id, body = {})
|
|
192
|
+
@http.request("POST", "/sms/campaigns/#{enc(id)}/send", body: body)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def cancel(id)
|
|
196
|
+
@http.request("POST", "/sms/campaigns/#{enc(id)}/cancel")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def pause(id)
|
|
200
|
+
@http.request("POST", "/sms/campaigns/#{enc(id)}/pause")
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def resume(id)
|
|
204
|
+
@http.request("POST", "/sms/campaigns/#{enc(id)}/resume")
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def reschedule(id, body)
|
|
208
|
+
@http.request("POST", "/sms/campaigns/#{enc(id)}/reschedule", body: body)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def validate(id)
|
|
212
|
+
@http.request("GET", "/sms/campaigns/#{enc(id)}/validate")
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# SMS subscription topics (independent from email topics); delete archives.
|
|
217
|
+
class SmsTopics < Base
|
|
218
|
+
def list(limit: nil, after: nil, before: nil)
|
|
219
|
+
@http.request("GET", "/sms/topics", query: list_params(limit: limit, after: after, before: before))
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def list_all(**params)
|
|
223
|
+
paginate("/sms/topics", params)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def create(body)
|
|
227
|
+
@http.request("POST", "/sms/topics", body: body)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def get(id)
|
|
231
|
+
@http.request("GET", "/sms/topics/#{enc(id)}")
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def update(id, body)
|
|
235
|
+
@http.request("PATCH", "/sms/topics/#{enc(id)}", body: body)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def delete(id)
|
|
239
|
+
@http.request("DELETE", "/sms/topics/#{enc(id)}")
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
49
243
|
class Email < Base
|
|
50
244
|
attr_reader :topics, :campaigns, :templates, :domains
|
|
51
245
|
|
|
@@ -152,6 +346,10 @@ module Unitpost
|
|
|
152
346
|
def get_import(id)
|
|
153
347
|
@http.request("GET", "/contacts/imports/#{enc(id)}")
|
|
154
348
|
end
|
|
349
|
+
|
|
350
|
+
def cancel_import(id)
|
|
351
|
+
@http.request("POST", "/contacts/imports/#{enc(id)}/cancel")
|
|
352
|
+
end
|
|
155
353
|
end
|
|
156
354
|
|
|
157
355
|
class ContactFields < Base
|
|
@@ -184,6 +382,14 @@ module Unitpost
|
|
|
184
382
|
end
|
|
185
383
|
end
|
|
186
384
|
|
|
385
|
+
class Jobs < Base
|
|
386
|
+
# A background job: the 202 answer of an operation too large for one
|
|
387
|
+
# request. Poll until `status` is COMPLETED, FAILED or CANCELED.
|
|
388
|
+
def get(id)
|
|
389
|
+
@http.request("GET", "/jobs/#{enc(id)}")
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
187
393
|
class Segments < Base
|
|
188
394
|
def list(limit: nil, after: nil, before: nil)
|
|
189
395
|
@http.request("GET", "/segments", query: list_params(limit: limit, after: after, before: before))
|
|
@@ -281,8 +487,11 @@ module Unitpost
|
|
|
281
487
|
@http.request("DELETE", "/email/campaigns/#{enc(id)}")
|
|
282
488
|
end
|
|
283
489
|
|
|
284
|
-
|
|
285
|
-
|
|
490
|
+
# Send a draft. `{}` arms its saved `scheduled_at` while that is still
|
|
491
|
+
# ahead (otherwise it sends NOW); `{ scheduled_at: "<ISO 8601>" }`
|
|
492
|
+
# schedules it; `{ scheduled_at: nil }` sends now.
|
|
493
|
+
def send(id, body = {})
|
|
494
|
+
@http.request("POST", "/email/campaigns/#{enc(id)}/send", body: body)
|
|
286
495
|
end
|
|
287
496
|
|
|
288
497
|
def cancel(id)
|
|
@@ -330,6 +539,11 @@ module Unitpost
|
|
|
330
539
|
def delete(id)
|
|
331
540
|
@http.request("DELETE", "/email/templates/#{enc(id)}")
|
|
332
541
|
end
|
|
542
|
+
|
|
543
|
+
# Send ONE test of the template to body[:to] with sample variables.
|
|
544
|
+
def test_send(id, body)
|
|
545
|
+
@http.request("POST", "/email/templates/#{enc(id)}/test-send", body: body)
|
|
546
|
+
end
|
|
333
547
|
end
|
|
334
548
|
|
|
335
549
|
# Read-only Brand Kit profiles (voice, colors, pinned /img/{id} graphics).
|
|
@@ -373,6 +587,11 @@ module Unitpost
|
|
|
373
587
|
def verify(id)
|
|
374
588
|
@http.request("POST", "/email/domains/#{enc(id)}/verify")
|
|
375
589
|
end
|
|
590
|
+
|
|
591
|
+
# Read-only DNS diagnosis: per-record answers and ranked findings with fixes.
|
|
592
|
+
def diagnose(id)
|
|
593
|
+
@http.request("GET", "/email/domains/#{enc(id)}/diagnose")
|
|
594
|
+
end
|
|
376
595
|
end
|
|
377
596
|
|
|
378
597
|
class Webhooks < Base
|
|
@@ -404,6 +623,19 @@ module Unitpost
|
|
|
404
623
|
@http.request("POST", "/webhooks/#{enc(id)}/test")
|
|
405
624
|
end
|
|
406
625
|
|
|
626
|
+
# New signing secret, returned once; the old one co-signs for 24 h.
|
|
627
|
+
def rotate_secret(id)
|
|
628
|
+
@http.request("POST", "/webhooks/#{enc(id)}/rotate-secret")
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
def list_deliveries(id, limit: nil, after: nil, before: nil)
|
|
632
|
+
@http.request("GET", "/webhooks/#{enc(id)}/deliveries", query: list_params(limit: limit, after: after, before: before))
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
def list_all_deliveries(id, **params)
|
|
636
|
+
paginate("/webhooks/#{enc(id)}/deliveries", params)
|
|
637
|
+
end
|
|
638
|
+
|
|
407
639
|
# Verify a delivery's signature and return the parsed event. Alias of
|
|
408
640
|
# Unitpost.verify_webhook / Unitpost::Webhooks.verify for a Resend-style
|
|
409
641
|
# client.webhooks.verify(...). Raises Unitpost::WebhookVerificationError on
|
data/lib/unitpost/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: unitpost
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Unitpost
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -30,6 +30,20 @@ dependencies:
|
|
|
30
30
|
- - "<"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
32
|
version: '3.0'
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: base64
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.1'
|
|
40
|
+
type: :runtime
|
|
41
|
+
prerelease: false
|
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0.1'
|
|
33
47
|
- !ruby/object:Gem::Dependency
|
|
34
48
|
name: rspec
|
|
35
49
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -70,6 +84,7 @@ files:
|
|
|
70
84
|
- lib/unitpost/client.rb
|
|
71
85
|
- lib/unitpost/errors.rb
|
|
72
86
|
- lib/unitpost/generated/operations.rb
|
|
87
|
+
- lib/unitpost/generated/sms_models.rb
|
|
73
88
|
- lib/unitpost/http_client.rb
|
|
74
89
|
- lib/unitpost/resources.rb
|
|
75
90
|
- lib/unitpost/version.rb
|