unitpost 0.3.0 → 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 +40 -3
- data/lib/unitpost/client.rb +6 -2
- data/lib/unitpost/generated/operations.rb +467 -19
- data/lib/unitpost/generated/sms_models.rb +222 -0
- data/lib/unitpost/resources.rb +213 -3
- 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
|
@@ -56,6 +56,7 @@ key at [unitpost.com → Settings → API keys](https://www.unitpost.com).
|
|
|
56
56
|
| `email.campaigns` | Draft, schedule, send, pause/resume |
|
|
57
57
|
| `email.templates` | Template CRUD |
|
|
58
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. |
|
|
59
60
|
| `contacts` | CRUD, import jobs |
|
|
60
61
|
| `contact_fields` | Custom field definitions |
|
|
61
62
|
| `segments` | Segments + membership |
|
|
@@ -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,6 +111,41 @@ 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
|
|
@@ -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,174 @@ 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
|
+
},
|
|
965
1175
|
{
|
|
966
1176
|
operation_id: "sms.get",
|
|
967
1177
|
resource: "sms",
|
|
@@ -983,13 +1193,41 @@ module Unitpost
|
|
|
983
1193
|
http_method: "GET",
|
|
984
1194
|
path: "/sms",
|
|
985
1195
|
path_params: [],
|
|
986
|
-
query_params: ["limit", "after", "before"],
|
|
1196
|
+
query_params: ["limit", "after", "before", "direction", "status", "from", "to", "participant", "contact_id", "campaign_id", "created_after", "created_before"],
|
|
987
1197
|
has_body: false,
|
|
988
1198
|
request_schema: nil,
|
|
989
1199
|
response_schema: nil,
|
|
990
1200
|
is_list: true,
|
|
991
1201
|
list_item_schema: "Sms"
|
|
992
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
|
+
},
|
|
993
1231
|
{
|
|
994
1232
|
operation_id: "sms.send",
|
|
995
1233
|
resource: "sms",
|
|
@@ -1004,6 +1242,188 @@ module Unitpost
|
|
|
1004
1242
|
is_list: false,
|
|
1005
1243
|
list_item_schema: nil
|
|
1006
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
|
+
},
|
|
1007
1427
|
{
|
|
1008
1428
|
operation_id: "suppressions.create",
|
|
1009
1429
|
resource: "suppressions",
|
|
@@ -1130,6 +1550,34 @@ module Unitpost
|
|
|
1130
1550
|
is_list: true,
|
|
1131
1551
|
list_item_schema: "Webhook"
|
|
1132
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
|
+
},
|
|
1133
1581
|
{
|
|
1134
1582
|
operation_id: "webhooks.test",
|
|
1135
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
|
|
@@ -47,7 +54,16 @@ module Unitpost
|
|
|
47
54
|
end
|
|
48
55
|
|
|
49
56
|
class Sms < Base
|
|
50
|
-
|
|
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
|
|
51
67
|
# prior express consent on record and honor recipient-local quiet hours.
|
|
52
68
|
# Billing is per segment. When no idempotency_key is given one is
|
|
53
69
|
# generated so a transient retry can't double-send.
|
|
@@ -59,6 +75,12 @@ module Unitpost
|
|
|
59
75
|
@http.request("GET", "/sms/#{enc(id)}")
|
|
60
76
|
end
|
|
61
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
|
+
|
|
62
84
|
def list(params = {})
|
|
63
85
|
@http.request("GET", "/sms", query: params)
|
|
64
86
|
end
|
|
@@ -66,6 +88,156 @@ module Unitpost
|
|
|
66
88
|
def list_all(params = {})
|
|
67
89
|
paginate("/sms", params)
|
|
68
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
|
|
69
241
|
end
|
|
70
242
|
|
|
71
243
|
class Email < Base
|
|
@@ -174,6 +346,10 @@ module Unitpost
|
|
|
174
346
|
def get_import(id)
|
|
175
347
|
@http.request("GET", "/contacts/imports/#{enc(id)}")
|
|
176
348
|
end
|
|
349
|
+
|
|
350
|
+
def cancel_import(id)
|
|
351
|
+
@http.request("POST", "/contacts/imports/#{enc(id)}/cancel")
|
|
352
|
+
end
|
|
177
353
|
end
|
|
178
354
|
|
|
179
355
|
class ContactFields < Base
|
|
@@ -206,6 +382,14 @@ module Unitpost
|
|
|
206
382
|
end
|
|
207
383
|
end
|
|
208
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
|
+
|
|
209
393
|
class Segments < Base
|
|
210
394
|
def list(limit: nil, after: nil, before: nil)
|
|
211
395
|
@http.request("GET", "/segments", query: list_params(limit: limit, after: after, before: before))
|
|
@@ -303,8 +487,11 @@ module Unitpost
|
|
|
303
487
|
@http.request("DELETE", "/email/campaigns/#{enc(id)}")
|
|
304
488
|
end
|
|
305
489
|
|
|
306
|
-
|
|
307
|
-
|
|
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)
|
|
308
495
|
end
|
|
309
496
|
|
|
310
497
|
def cancel(id)
|
|
@@ -352,6 +539,11 @@ module Unitpost
|
|
|
352
539
|
def delete(id)
|
|
353
540
|
@http.request("DELETE", "/email/templates/#{enc(id)}")
|
|
354
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
|
|
355
547
|
end
|
|
356
548
|
|
|
357
549
|
# Read-only Brand Kit profiles (voice, colors, pinned /img/{id} graphics).
|
|
@@ -395,6 +587,11 @@ module Unitpost
|
|
|
395
587
|
def verify(id)
|
|
396
588
|
@http.request("POST", "/email/domains/#{enc(id)}/verify")
|
|
397
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
|
|
398
595
|
end
|
|
399
596
|
|
|
400
597
|
class Webhooks < Base
|
|
@@ -426,6 +623,19 @@ module Unitpost
|
|
|
426
623
|
@http.request("POST", "/webhooks/#{enc(id)}/test")
|
|
427
624
|
end
|
|
428
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
|
+
|
|
429
639
|
# Verify a delivery's signature and return the parsed event. Alias of
|
|
430
640
|
# Unitpost.verify_webhook / Unitpost::Webhooks.verify for a Resend-style
|
|
431
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
|