trycourier 4.7.1 → 4.8.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.
Files changed (260) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/README.md +25 -235
  4. data/lib/courier/client.rb +4 -0
  5. data/lib/courier/internal/util.rb +31 -0
  6. data/lib/courier/models/audience_delete_params.rb +7 -1
  7. data/lib/courier/models/audience_list_members_params.rb +8 -1
  8. data/lib/courier/models/audience_retrieve_params.rb +7 -1
  9. data/lib/courier/models/audience_update_params.rb +8 -1
  10. data/lib/courier/models/audit_event_retrieve_params.rb +7 -1
  11. data/lib/courier/models/automations/invoke_invoke_by_template_params.rb +7 -1
  12. data/lib/courier/models/brand_delete_params.rb +7 -1
  13. data/lib/courier/models/brand_retrieve_params.rb +7 -1
  14. data/lib/courier/models/brand_update_params.rb +8 -1
  15. data/lib/courier/models/bulk_add_users_params.rb +7 -1
  16. data/lib/courier/models/bulk_list_users_params.rb +8 -1
  17. data/lib/courier/models/bulk_retrieve_job_params.rb +7 -1
  18. data/lib/courier/models/bulk_run_job_params.rb +7 -1
  19. data/lib/courier/models/elemental_content.rb +1 -8
  20. data/lib/courier/models/journey.rb +63 -0
  21. data/lib/courier/models/journey_invoke_params.rb +20 -0
  22. data/lib/courier/models/journey_list_params.rb +47 -0
  23. data/lib/courier/models/journeys_invoke_request.rb +49 -0
  24. data/lib/courier/models/journeys_invoke_response.rb +17 -0
  25. data/lib/courier/models/journeys_list_response.rb +24 -0
  26. data/lib/courier/models/list_delete_params.rb +7 -1
  27. data/lib/courier/models/list_restore_params.rb +7 -1
  28. data/lib/courier/models/list_retrieve_params.rb +7 -1
  29. data/lib/courier/models/list_update_params.rb +7 -1
  30. data/lib/courier/models/lists/subscription_add_params.rb +7 -1
  31. data/lib/courier/models/lists/subscription_list_params.rb +8 -1
  32. data/lib/courier/models/lists/subscription_subscribe_params.rb +7 -1
  33. data/lib/courier/models/lists/subscription_subscribe_user_params.rb +7 -1
  34. data/lib/courier/models/lists/subscription_unsubscribe_user_params.rb +7 -1
  35. data/lib/courier/models/message_cancel_params.rb +7 -1
  36. data/lib/courier/models/message_content_params.rb +7 -1
  37. data/lib/courier/models/message_details.rb +37 -37
  38. data/lib/courier/models/message_history_params.rb +8 -1
  39. data/lib/courier/models/message_retrieve_params.rb +7 -1
  40. data/lib/courier/models/notification_retrieve_content_params.rb +7 -1
  41. data/lib/courier/models/notifications/check_delete_params.rb +7 -1
  42. data/lib/courier/models/notifications/check_list_params.rb +7 -1
  43. data/lib/courier/models/notifications/check_update_params.rb +7 -1
  44. data/lib/courier/models/notifications/draft_retrieve_content_params.rb +7 -1
  45. data/lib/courier/models/profile_create_params.rb +7 -1
  46. data/lib/courier/models/profile_delete_params.rb +7 -1
  47. data/lib/courier/models/profile_replace_params.rb +7 -1
  48. data/lib/courier/models/profile_retrieve_params.rb +7 -1
  49. data/lib/courier/models/profile_update_params.rb +8 -1
  50. data/lib/courier/models/profiles/list_delete_params.rb +7 -1
  51. data/lib/courier/models/profiles/list_retrieve_params.rb +8 -1
  52. data/lib/courier/models/profiles/list_subscribe_params.rb +7 -1
  53. data/lib/courier/models/request_archive_params.rb +7 -1
  54. data/lib/courier/models/tenant_delete_params.rb +7 -1
  55. data/lib/courier/models/tenant_list_users_params.rb +8 -1
  56. data/lib/courier/models/tenant_retrieve_params.rb +7 -1
  57. data/lib/courier/models/tenant_update_params.rb +8 -1
  58. data/lib/courier/models/tenants/preferences/item_delete_params.rb +7 -1
  59. data/lib/courier/models/tenants/preferences/item_update_params.rb +7 -1
  60. data/lib/courier/models/tenants/template_list_params.rb +8 -1
  61. data/lib/courier/models/tenants/template_publish_params.rb +7 -1
  62. data/lib/courier/models/tenants/template_replace_params.rb +7 -1
  63. data/lib/courier/models/tenants/template_retrieve_params.rb +7 -1
  64. data/lib/courier/models/tenants/templates/version_retrieve_params.rb +7 -1
  65. data/lib/courier/models/translation_retrieve_params.rb +7 -1
  66. data/lib/courier/models/translation_update_params.rb +7 -1
  67. data/lib/courier/models/users/preference_retrieve_params.rb +8 -1
  68. data/lib/courier/models/users/preference_retrieve_topic_params.rb +8 -1
  69. data/lib/courier/models/users/preference_update_or_create_topic_params.rb +8 -1
  70. data/lib/courier/models/users/tenant_add_multiple_params.rb +7 -1
  71. data/lib/courier/models/users/tenant_add_single_params.rb +7 -1
  72. data/lib/courier/models/users/tenant_list_params.rb +8 -1
  73. data/lib/courier/models/users/tenant_remove_all_params.rb +7 -1
  74. data/lib/courier/models/users/tenant_remove_single_params.rb +7 -1
  75. data/lib/courier/models/users/token_add_multiple_params.rb +7 -1
  76. data/lib/courier/models/users/token_add_single_params.rb +168 -2
  77. data/lib/courier/models/users/token_delete_params.rb +7 -1
  78. data/lib/courier/models/users/token_list_params.rb +7 -1
  79. data/lib/courier/models/users/token_retrieve_params.rb +7 -1
  80. data/lib/courier/models/users/token_update_params.rb +7 -1
  81. data/lib/courier/models.rb +12 -0
  82. data/lib/courier/resources/audiences.rb +4 -2
  83. data/lib/courier/resources/audit_events.rb +2 -1
  84. data/lib/courier/resources/automations.rb +2 -1
  85. data/lib/courier/resources/brands.rb +2 -1
  86. data/lib/courier/resources/bulk.rb +2 -1
  87. data/lib/courier/resources/journeys.rb +73 -0
  88. data/lib/courier/resources/lists/subscriptions.rb +2 -1
  89. data/lib/courier/resources/lists.rb +2 -1
  90. data/lib/courier/resources/messages.rb +4 -2
  91. data/lib/courier/resources/notifications.rb +2 -1
  92. data/lib/courier/resources/profiles/lists.rb +2 -1
  93. data/lib/courier/resources/tenants/templates.rb +2 -1
  94. data/lib/courier/resources/tenants.rb +4 -2
  95. data/lib/courier/resources/users/preferences.rb +7 -4
  96. data/lib/courier/resources/users/tenants.rb +2 -1
  97. data/lib/courier/resources/users/tokens.rb +7 -9
  98. data/lib/courier/version.rb +1 -1
  99. data/lib/courier.rb +7 -0
  100. data/rbi/courier/client.rbi +3 -0
  101. data/rbi/courier/internal/util.rbi +20 -0
  102. data/rbi/courier/models/audience_delete_params.rbi +13 -5
  103. data/rbi/courier/models/audience_list_members_params.rbi +6 -0
  104. data/rbi/courier/models/audience_retrieve_params.rbi +13 -5
  105. data/rbi/courier/models/audience_update_params.rbi +6 -0
  106. data/rbi/courier/models/audit_event_retrieve_params.rbi +13 -5
  107. data/rbi/courier/models/automations/invoke_invoke_by_template_params.rbi +6 -0
  108. data/rbi/courier/models/brand_delete_params.rbi +13 -5
  109. data/rbi/courier/models/brand_retrieve_params.rbi +13 -5
  110. data/rbi/courier/models/brand_update_params.rbi +6 -0
  111. data/rbi/courier/models/bulk_add_users_params.rbi +6 -1
  112. data/rbi/courier/models/bulk_list_users_params.rbi +6 -0
  113. data/rbi/courier/models/bulk_retrieve_job_params.rbi +13 -5
  114. data/rbi/courier/models/bulk_run_job_params.rbi +13 -5
  115. data/rbi/courier/models/elemental_content.rbi +3 -9
  116. data/rbi/courier/models/journey.rbi +91 -0
  117. data/rbi/courier/models/journey_invoke_params.rbi +35 -0
  118. data/rbi/courier/models/journey_list_params.rbi +85 -0
  119. data/rbi/courier/models/journeys_invoke_request.rbi +84 -0
  120. data/rbi/courier/models/journeys_invoke_response.rbi +27 -0
  121. data/rbi/courier/models/journeys_list_response.rbi +46 -0
  122. data/rbi/courier/models/list_delete_params.rbi +13 -5
  123. data/rbi/courier/models/list_restore_params.rbi +13 -5
  124. data/rbi/courier/models/list_retrieve_params.rbi +13 -5
  125. data/rbi/courier/models/list_update_params.rbi +6 -1
  126. data/rbi/courier/models/lists/subscription_add_params.rbi +6 -1
  127. data/rbi/courier/models/lists/subscription_list_params.rbi +6 -0
  128. data/rbi/courier/models/lists/subscription_subscribe_params.rbi +6 -1
  129. data/rbi/courier/models/lists/subscription_subscribe_user_params.rbi +6 -1
  130. data/rbi/courier/models/lists/subscription_unsubscribe_user_params.rbi +10 -2
  131. data/rbi/courier/models/message_cancel_params.rbi +13 -5
  132. data/rbi/courier/models/message_content_params.rbi +13 -5
  133. data/rbi/courier/models/message_details.rbi +55 -43
  134. data/rbi/courier/models/message_history_params.rbi +10 -1
  135. data/rbi/courier/models/message_retrieve_params.rbi +13 -5
  136. data/rbi/courier/models/notification_retrieve_content_params.rbi +13 -5
  137. data/rbi/courier/models/notifications/check_delete_params.rbi +10 -2
  138. data/rbi/courier/models/notifications/check_list_params.rbi +10 -2
  139. data/rbi/courier/models/notifications/check_update_params.rbi +6 -1
  140. data/rbi/courier/models/notifications/draft_retrieve_content_params.rbi +13 -5
  141. data/rbi/courier/models/profile_create_params.rbi +6 -1
  142. data/rbi/courier/models/profile_delete_params.rbi +13 -5
  143. data/rbi/courier/models/profile_replace_params.rbi +6 -1
  144. data/rbi/courier/models/profile_retrieve_params.rbi +13 -5
  145. data/rbi/courier/models/profile_update_params.rbi +6 -0
  146. data/rbi/courier/models/profiles/list_delete_params.rbi +13 -5
  147. data/rbi/courier/models/profiles/list_retrieve_params.rbi +6 -0
  148. data/rbi/courier/models/profiles/list_subscribe_params.rbi +6 -1
  149. data/rbi/courier/models/request_archive_params.rbi +13 -5
  150. data/rbi/courier/models/tenant_delete_params.rbi +13 -5
  151. data/rbi/courier/models/tenant_list_users_params.rbi +6 -0
  152. data/rbi/courier/models/tenant_retrieve_params.rbi +13 -5
  153. data/rbi/courier/models/tenant_update_params.rbi +6 -0
  154. data/rbi/courier/models/tenants/preferences/item_delete_params.rbi +10 -2
  155. data/rbi/courier/models/tenants/preferences/item_update_params.rbi +10 -2
  156. data/rbi/courier/models/tenants/template_list_params.rbi +6 -0
  157. data/rbi/courier/models/tenants/template_publish_params.rbi +10 -2
  158. data/rbi/courier/models/tenants/template_replace_params.rbi +10 -2
  159. data/rbi/courier/models/tenants/template_retrieve_params.rbi +10 -2
  160. data/rbi/courier/models/tenants/templates/version_retrieve_params.rbi +6 -1
  161. data/rbi/courier/models/translation_retrieve_params.rbi +10 -2
  162. data/rbi/courier/models/translation_update_params.rbi +6 -1
  163. data/rbi/courier/models/users/preference_retrieve_params.rbi +6 -0
  164. data/rbi/courier/models/users/preference_retrieve_topic_params.rbi +6 -0
  165. data/rbi/courier/models/users/preference_update_or_create_topic_params.rbi +6 -0
  166. data/rbi/courier/models/users/tenant_add_multiple_params.rbi +6 -1
  167. data/rbi/courier/models/users/tenant_add_single_params.rbi +6 -1
  168. data/rbi/courier/models/users/tenant_list_params.rbi +6 -0
  169. data/rbi/courier/models/users/tenant_remove_all_params.rbi +13 -5
  170. data/rbi/courier/models/users/tenant_remove_single_params.rbi +10 -2
  171. data/rbi/courier/models/users/token_add_multiple_params.rbi +13 -5
  172. data/rbi/courier/models/users/token_add_single_params.rbi +290 -3
  173. data/rbi/courier/models/users/token_delete_params.rbi +10 -2
  174. data/rbi/courier/models/users/token_list_params.rbi +13 -5
  175. data/rbi/courier/models/users/token_retrieve_params.rbi +10 -2
  176. data/rbi/courier/models/users/token_update_params.rbi +6 -1
  177. data/rbi/courier/models.rbi +12 -0
  178. data/rbi/courier/resources/journeys.rbi +64 -0
  179. data/rbi/courier/resources/users/tokens.rbi +11 -9
  180. data/sig/courier/client.rbs +2 -0
  181. data/sig/courier/internal/util.rbs +10 -0
  182. data/sig/courier/models/audience_delete_params.rbs +11 -3
  183. data/sig/courier/models/audience_list_members_params.rbs +6 -1
  184. data/sig/courier/models/audience_retrieve_params.rbs +11 -3
  185. data/sig/courier/models/audience_update_params.rbs +5 -0
  186. data/sig/courier/models/audit_event_retrieve_params.rbs +11 -3
  187. data/sig/courier/models/automations/invoke_invoke_by_template_params.rbs +5 -0
  188. data/sig/courier/models/brand_delete_params.rbs +11 -3
  189. data/sig/courier/models/brand_retrieve_params.rbs +11 -3
  190. data/sig/courier/models/brand_update_params.rbs +5 -0
  191. data/sig/courier/models/bulk_add_users_params.rbs +5 -1
  192. data/sig/courier/models/bulk_list_users_params.rbs +6 -1
  193. data/sig/courier/models/bulk_retrieve_job_params.rbs +11 -3
  194. data/sig/courier/models/bulk_run_job_params.rbs +11 -3
  195. data/sig/courier/models/elemental_content.rbs +3 -11
  196. data/sig/courier/models/journey.rbs +55 -0
  197. data/sig/courier/models/journey_invoke_params.rbs +25 -0
  198. data/sig/courier/models/journey_list_params.rbs +45 -0
  199. data/sig/courier/models/journeys_invoke_request.rbs +36 -0
  200. data/sig/courier/models/journeys_invoke_response.rbs +13 -0
  201. data/sig/courier/models/journeys_list_response.rbs +23 -0
  202. data/sig/courier/models/list_delete_params.rbs +12 -3
  203. data/sig/courier/models/list_restore_params.rbs +11 -3
  204. data/sig/courier/models/list_retrieve_params.rbs +11 -3
  205. data/sig/courier/models/list_update_params.rbs +9 -1
  206. data/sig/courier/models/lists/subscription_add_params.rbs +8 -1
  207. data/sig/courier/models/lists/subscription_list_params.rbs +6 -1
  208. data/sig/courier/models/lists/subscription_subscribe_params.rbs +8 -1
  209. data/sig/courier/models/lists/subscription_subscribe_user_params.rbs +9 -1
  210. data/sig/courier/models/lists/subscription_unsubscribe_user_params.rbs +6 -1
  211. data/sig/courier/models/message_cancel_params.rbs +11 -3
  212. data/sig/courier/models/message_content_params.rbs +11 -3
  213. data/sig/courier/models/message_details.rbs +31 -23
  214. data/sig/courier/models/message_history_params.rbs +6 -1
  215. data/sig/courier/models/message_retrieve_params.rbs +11 -3
  216. data/sig/courier/models/notification_retrieve_content_params.rbs +8 -3
  217. data/sig/courier/models/notifications/check_delete_params.rbs +10 -2
  218. data/sig/courier/models/notifications/check_list_params.rbs +10 -2
  219. data/sig/courier/models/notifications/check_update_params.rbs +9 -1
  220. data/sig/courier/models/notifications/draft_retrieve_content_params.rbs +8 -3
  221. data/sig/courier/models/profile_create_params.rbs +5 -1
  222. data/sig/courier/models/profile_delete_params.rbs +11 -3
  223. data/sig/courier/models/profile_replace_params.rbs +5 -1
  224. data/sig/courier/models/profile_retrieve_params.rbs +11 -3
  225. data/sig/courier/models/profile_update_params.rbs +5 -1
  226. data/sig/courier/models/profiles/list_delete_params.rbs +11 -3
  227. data/sig/courier/models/profiles/list_retrieve_params.rbs +6 -1
  228. data/sig/courier/models/profiles/list_subscribe_params.rbs +8 -1
  229. data/sig/courier/models/request_archive_params.rbs +11 -3
  230. data/sig/courier/models/tenant_delete_params.rbs +11 -3
  231. data/sig/courier/models/tenant_list_users_params.rbs +5 -1
  232. data/sig/courier/models/tenant_retrieve_params.rbs +11 -3
  233. data/sig/courier/models/tenant_update_params.rbs +5 -0
  234. data/sig/courier/models/tenants/preferences/item_delete_params.rbs +6 -1
  235. data/sig/courier/models/tenants/preferences/item_update_params.rbs +8 -1
  236. data/sig/courier/models/tenants/template_list_params.rbs +5 -1
  237. data/sig/courier/models/tenants/template_publish_params.rbs +8 -1
  238. data/sig/courier/models/tenants/template_replace_params.rbs +8 -1
  239. data/sig/courier/models/tenants/template_retrieve_params.rbs +6 -1
  240. data/sig/courier/models/tenants/templates/version_retrieve_params.rbs +5 -1
  241. data/sig/courier/models/translation_retrieve_params.rbs +6 -1
  242. data/sig/courier/models/translation_update_params.rbs +5 -1
  243. data/sig/courier/models/users/preference_retrieve_params.rbs +6 -1
  244. data/sig/courier/models/users/preference_retrieve_topic_params.rbs +5 -1
  245. data/sig/courier/models/users/preference_update_or_create_topic_params.rbs +5 -0
  246. data/sig/courier/models/users/tenant_add_multiple_params.rbs +5 -1
  247. data/sig/courier/models/users/tenant_add_single_params.rbs +5 -1
  248. data/sig/courier/models/users/tenant_list_params.rbs +5 -1
  249. data/sig/courier/models/users/tenant_remove_all_params.rbs +11 -3
  250. data/sig/courier/models/users/tenant_remove_single_params.rbs +6 -1
  251. data/sig/courier/models/users/token_add_multiple_params.rbs +11 -3
  252. data/sig/courier/models/users/token_add_single_params.rbs +127 -4
  253. data/sig/courier/models/users/token_delete_params.rbs +6 -1
  254. data/sig/courier/models/users/token_list_params.rbs +11 -3
  255. data/sig/courier/models/users/token_retrieve_params.rbs +6 -1
  256. data/sig/courier/models/users/token_update_params.rbs +5 -0
  257. data/sig/courier/models.rbs +12 -0
  258. data/sig/courier/resources/journeys.rbs +21 -0
  259. data/sig/courier/resources/users/tokens.rbs +5 -6
  260. metadata +23 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 60a3e529dea2838c4e1a907cb78d1e593eb5aa2574515b56e3923968b73ee6fb
4
- data.tar.gz: b9112b225ef1284261bc91162a93929bca085cb983241ccffceb4ab9527892d1
3
+ metadata.gz: 453a5c5183a4ecfeebab4f86e963ac204c115741c2fedb0f32156b1a6aff23f0
4
+ data.tar.gz: 4db69717cb3bdcd63d2eae022325c4ae14fe8a1203cec1a7212628c3804242ce
5
5
  SHA512:
6
- metadata.gz: 2dbc06288c898543b75c19fe26285f7897a11087cebea941cd71eee7458615d3d156bd4e2bfe33ca4f20aa3825d041e30a9f2a7f6b71a83b9523a57839dfa772
7
- data.tar.gz: 5fb3b16785c63052748c36af642ec13bf67c7743264060088953d914bcb799a7deb040c066f5b06ae334a16b45dae28e5bef736ab2760f12af3a34ce9f441381
6
+ metadata.gz: 7931be3dcf151f5e26313fcfd94b25995f416bd17147e40d99dec981991ebaa2b9a2d0ac3534f2d3888c60d969ec32cea8b8e07a2e6656fbf2e33526a3a041e4
7
+ data.tar.gz: 4d57d68375783ca713d41844d364c04e5f234834509fbd465822ca89aa9f0044e558055d730cbacaf50ed5d96d4839aa7087a4436c497c401d985658e08a3bd1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.8.0 (2026-03-12)
4
+
5
+ Full Changelog: [v4.7.1...v4.8.0](https://github.com/trycourier/courier-ruby/compare/v4.7.1...v4.8.0)
6
+
7
+ ### Features
8
+
9
+ * **api:** add journeys resource with list and invoke methods ([26a80de](https://github.com/trycourier/courier-ruby/commit/26a80de1aa75c947f63e09da0ae7cb539947ecc4))
10
+ * **api:** add provider_key/device/tracking to token params, remove body_token ([acb3031](https://github.com/trycourier/courier-ruby/commit/acb30313188a385e2ad39be4f4040e4e5836106d))
11
+ * **api:** remove brand field from ElementalContent model ([519fcc0](https://github.com/trycourier/courier-ruby/commit/519fcc0a2f35a1fd61e64362b5a2e7b2d8225232))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * mark MessageDetails timestamp fields as optional ([31a0be6](https://github.com/trycourier/courier-ruby/commit/31a0be63ee9969176a7bb3a627eabeb7c26d50f6))
17
+
18
+
19
+ ### Chores
20
+
21
+ * **ci:** add build step ([d07ae3d](https://github.com/trycourier/courier-ruby/commit/d07ae3dc073f38ac30af1311f1d70153e441bf51))
22
+ * **ci:** skip uploading artifacts on stainless-internal branches ([d4c0636](https://github.com/trycourier/courier-ruby/commit/d4c06361e5ef4e7c0a3a1660215d63fe845694c5))
23
+ * **internal:** codegen related update ([3a0f51e](https://github.com/trycourier/courier-ruby/commit/3a0f51e6a03c33097f1307ac7974a3c5ca72ac90))
24
+ * **internal:** codegen related update ([1cce4a8](https://github.com/trycourier/courier-ruby/commit/1cce4a8be23782aae7fb9185b8ac2efd06516842))
25
+ * **internal:** remove mock server code ([a81351a](https://github.com/trycourier/courier-ruby/commit/a81351a2fde27b8a785a79436394a991e9048d97))
26
+ * update mock server docs ([62d69fc](https://github.com/trycourier/courier-ruby/commit/62d69fc657babf8ff535e150d74a2283ddb64459))
27
+
28
+
29
+ ### Documentation
30
+
31
+ * add AUTO-GENERATED-OVERVIEW markers for README sync ([#75](https://github.com/trycourier/courier-ruby/issues/75)) ([ab86102](https://github.com/trycourier/courier-ruby/commit/ab86102437d2af03dc45beae7262371c70b6847f))
32
+ * sync README from mintlify-docs (2026-02-20 18:11 UTC) ([#76](https://github.com/trycourier/courier-ruby/issues/76)) ([cc67458](https://github.com/trycourier/courier-ruby/commit/cc6745816e152b890b84caaf666178bdbd9d3192))
33
+
3
34
  ## 4.7.1 (2026-02-07)
4
35
 
5
36
  Full Changelog: [v4.7.0...v4.7.1](https://github.com/trycourier/courier-ruby/compare/v4.7.0...v4.7.1)
data/README.md CHANGED
@@ -1,259 +1,49 @@
1
- # Courier Ruby API library
1
+ <!-- AUTO-GENERATED-OVERVIEW:START Do not edit this section. It is synced from mintlify-docs. -->
2
+ # Courier Ruby SDK
2
3
 
3
- The Courier Ruby library provides convenient access to the Courier REST API from any Ruby 3.2.0+ application. It ships with comprehensive types & docstrings in Yard, RBS, and RBI [see below](https://github.com/trycourier/courier-ruby#Sorbet) for usage with Sorbet. The standard library's `net/http` is used as the HTTP transport, with connection pooling via the `connection_pool` gem.
4
-
5
- It is generated with [Stainless](https://www.stainless.com/).
6
-
7
- ## Documentation
8
-
9
- Documentation for releases of this gem can be found [on RubyDoc](https://gemdocs.org/gems/trycourier).
10
-
11
- The REST API documentation can be found on [www.courier.com](https://www.courier.com/docs).
4
+ The Courier Ruby SDK provides typed access to the Courier REST API from any Ruby 3.2+ application. It ships with Yard docstrings, RBS and RBI type definitions for Sorbet, and uses `net/http` with connection pooling.
12
5
 
13
6
  ## Installation
14
7
 
15
- To use this gem, install via Bundler by adding the following to your application's `Gemfile`:
16
-
17
- <!-- x-release-please-start-version -->
8
+ Add to your `Gemfile`:
18
9
 
19
10
  ```ruby
20
- gem "trycourier", "~> 4.7.1"
11
+ gem "trycourier", "~> 4.7"
21
12
  ```
22
13
 
23
- <!-- x-release-please-end -->
14
+ Then run `bundle install`.
24
15
 
25
- ## Usage
16
+ ## Quick Start
26
17
 
27
18
  ```ruby
28
19
  require "bundler/setup"
29
20
  require "courier"
30
21
 
31
- courier = Courier::Client.new(
32
- api_key: ENV["COURIER_API_KEY"] # This is the default and can be omitted
33
- )
34
-
35
- response = courier.send_.message(
36
- message: {to: {user_id: "your_user_id"}, template: "your_template_id", data: {foo: "bar"}}
37
- )
38
-
39
- puts(response.requestId)
40
- ```
41
-
42
- ### Handling errors
43
-
44
- When the library is unable to connect to the API, or if the API returns a non-success status code (i.e., 4xx or 5xx response), a subclass of `Courier::Errors::APIError` will be thrown:
45
-
46
- ```ruby
47
- begin
48
- send_ = courier.send_.message(
49
- message: {to: {user_id: "your_user_id"}, template: "your_template_id", data: {foo: "bar"}}
50
- )
51
- rescue Courier::Errors::APIConnectionError => e
52
- puts("The server could not be reached")
53
- puts(e.cause) # an underlying Exception, likely raised within `net/http`
54
- rescue Courier::Errors::RateLimitError => e
55
- puts("A 429 status code was received; we should back off a bit.")
56
- rescue Courier::Errors::APIStatusError => e
57
- puts("Another non-200-range status code was received")
58
- puts(e.status)
59
- end
60
- ```
61
-
62
- Error codes are as follows:
63
-
64
- | Cause | Error Type |
65
- | ---------------- | -------------------------- |
66
- | HTTP 400 | `BadRequestError` |
67
- | HTTP 401 | `AuthenticationError` |
68
- | HTTP 403 | `PermissionDeniedError` |
69
- | HTTP 404 | `NotFoundError` |
70
- | HTTP 409 | `ConflictError` |
71
- | HTTP 422 | `UnprocessableEntityError` |
72
- | HTTP 429 | `RateLimitError` |
73
- | HTTP >= 500 | `InternalServerError` |
74
- | Other HTTP error | `APIStatusError` |
75
- | Timeout | `APITimeoutError` |
76
- | Network error | `APIConnectionError` |
77
-
78
- ### Retries
79
-
80
- Certain errors will be automatically retried 2 times by default, with a short exponential backoff.
81
-
82
- Connection errors (for example, due to a network connectivity problem), 408 Request Timeout, 409 Conflict, 429 Rate Limit, >=500 Internal errors, and timeouts will all be retried by default.
83
-
84
- You can use the `max_retries` option to configure or disable this:
85
-
86
- ```ruby
87
- # Configure the default for all requests:
88
- courier = Courier::Client.new(
89
- max_retries: 0 # default is 2
90
- )
91
-
92
- # Or, configure per-request:
93
- courier.send_.message(
94
- message: {to: {user_id: "your_user_id"}, template: "your_template_id", data: {foo: "bar"}},
95
- request_options: {max_retries: 5}
96
- )
97
- ```
98
-
99
- ### Timeouts
100
-
101
- By default, requests will time out after 60 seconds. You can use the timeout option to configure or disable this:
102
-
103
- ```ruby
104
- # Configure the default for all requests:
105
- courier = Courier::Client.new(
106
- timeout: nil # default is 60
107
- )
108
-
109
- # Or, configure per-request:
110
- courier.send_.message(
111
- message: {to: {user_id: "your_user_id"}, template: "your_template_id", data: {foo: "bar"}},
112
- request_options: {timeout: 5}
113
- )
114
- ```
115
-
116
- On timeout, `Courier::Errors::APITimeoutError` is raised.
117
-
118
- Note that requests that time out are retried by default.
119
-
120
- ## Advanced concepts
121
-
122
- ### BaseModel
123
-
124
- All parameter and response objects inherit from `Courier::Internal::Type::BaseModel`, which provides several conveniences, including:
125
-
126
- 1. All fields, including unknown ones, are accessible with `obj[:prop]` syntax, and can be destructured with `obj => {prop: prop}` or pattern-matching syntax.
127
-
128
- 2. Structural equivalence for equality; if two API calls return the same values, comparing the responses with == will return true.
129
-
130
- 3. Both instances and the classes themselves can be pretty-printed.
131
-
132
- 4. Helpers such as `#to_h`, `#deep_to_h`, `#to_json`, and `#to_yaml`.
133
-
134
- ### Making custom or undocumented requests
22
+ client = Courier::Client.new
135
23
 
136
- #### Undocumented properties
137
-
138
- You can send undocumented parameters to any endpoint, and read undocumented response properties, like so:
139
-
140
- Note: the `extra_` parameters of the same name overrides the documented parameters.
141
-
142
- ```ruby
143
- response =
144
- courier.send_.message(
145
- message: {to: {user_id: "your_user_id"}, template: "your_template_id", data: {foo: "bar"}},
146
- request_options: {
147
- extra_query: {my_query_parameter: value},
148
- extra_body: {my_body_parameter: value},
149
- extra_headers: {"my-header": value}
24
+ response = client.send_.message(
25
+ message: {
26
+ to: { email: "you@example.com" },
27
+ content: {
28
+ title: "Hello from Courier!",
29
+ body: "Your first notification, sent with the Ruby SDK."
150
30
  }
151
- )
152
-
153
- puts(response[:my_undocumented_property])
154
- ```
155
-
156
- #### Undocumented request params
157
-
158
- If you want to explicitly send an extra param, you can do so with the `extra_query`, `extra_body`, and `extra_headers` under the `request_options:` parameter when making a request, as seen in the examples above.
159
-
160
- #### Undocumented endpoints
161
-
162
- To make requests to undocumented endpoints while retaining the benefit of auth, retries, and so on, you can make requests using `client.request`, like so:
163
-
164
- ```ruby
165
- response = client.request(
166
- method: :post,
167
- path: '/undocumented/endpoint',
168
- query: {"dog": "woof"},
169
- headers: {"useful-header": "interesting-value"},
170
- body: {"hello": "world"}
171
- )
172
- ```
173
-
174
- ### Concurrency & connection pooling
175
-
176
- The `Courier::Client` instances are threadsafe, but are only are fork-safe when there are no in-flight HTTP requests.
177
-
178
- Each instance of `Courier::Client` has its own HTTP connection pool with a default size of 99. As such, we recommend instantiating the client once per application in most settings.
179
-
180
- When all available connections from the pool are checked out, requests wait for a new connection to become available, with queue time counting towards the request timeout.
181
-
182
- Unless otherwise specified, other classes in the SDK do not have locks protecting their underlying data structure.
183
-
184
- ## Sorbet
185
-
186
- This library provides comprehensive [RBI](https://sorbet.org/docs/rbi) definitions, and has no dependency on sorbet-runtime.
187
-
188
- You can provide typesafe request parameters like so:
189
-
190
- ```ruby
191
- courier.send_.message(
192
- message: Courier::SendMessageParams::Message.new(
193
- to: Courier::UserRecipient.new(user_id: "your_user_id"),
194
- template: "your_template_id",
195
- data: {foo: "bar"}
196
- )
197
- )
198
- ```
199
-
200
- Or, equivalently:
201
-
202
- ```ruby
203
- # Hashes work, but are not typesafe:
204
- courier.send_.message(
205
- message: {to: {user_id: "your_user_id"}, template: "your_template_id", data: {foo: "bar"}}
31
+ }
206
32
  )
207
33
 
208
- # You can also splat a full Params class:
209
- params = Courier::SendMessageParams.new(
210
- message: Courier::SendMessageParams::Message.new(
211
- to: Courier::UserRecipient.new(user_id: "your_user_id"),
212
- template: "your_template_id",
213
- data: {foo: "bar"}
214
- )
215
- )
216
- courier.send_.message(**params)
34
+ puts response.request_id
217
35
  ```
218
36
 
219
- ### Enums
220
-
221
- Since this library does not depend on `sorbet-runtime`, it cannot provide [`T::Enum`](https://sorbet.org/docs/tenum) instances. Instead, we provide "tagged symbols" instead, which is always a primitive at runtime:
222
-
223
- ```ruby
224
- # :AND
225
- puts(Courier::AudienceUpdateParams::Operator::AND)
226
-
227
- # Revealed type: `T.all(Courier::AudienceUpdateParams::Operator, Symbol)`
228
- T.reveal_type(Courier::AudienceUpdateParams::Operator::AND)
229
- ```
37
+ The client reads `COURIER_API_KEY` from your environment automatically. You can also pass it explicitly: `Courier::Client.new(api_key: "your-key")`.
230
38
 
231
- Enum parameters have a "relaxed" type, so you can either pass in enum constants or their literal value:
39
+ > **Note**: The method is `send_` (with trailing underscore) because `send` is a reserved method in Ruby.
232
40
 
233
- ```ruby
234
- # Using the enum constants preserves the tagged type information:
235
- courier.audiences.update(
236
- operator: Courier::AudienceUpdateParams::Operator::AND,
237
- # …
238
- )
239
-
240
- # Literal values are also permissible:
241
- courier.audiences.update(
242
- operator: :AND,
243
- # …
244
- )
245
- ```
246
-
247
- ## Versioning
248
-
249
- This package follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions. As the library is in initial development and has a major version of `0`, APIs may change at any time.
250
-
251
- This package considers improvements to the (non-runtime) `*.rbi` and `*.rbs` type definitions to be non-breaking changes.
252
-
253
- ## Requirements
254
-
255
- Ruby 3.2.0 or higher.
41
+ ## Documentation
256
42
 
257
- ## Contributing
43
+ Full documentation: **[courier.com/docs/sdk-libraries/ruby](https://www.courier.com/docs/sdk-libraries/ruby/)**
258
44
 
259
- See [the contributing documentation](https://github.com/trycourier/courier-ruby/tree/main/CONTRIBUTING.md).
45
+ - [Quickstart](https://www.courier.com/docs/getting-started/quickstart/)
46
+ - [Send API](https://www.courier.com/docs/platform/sending/send-message/)
47
+ - [API Reference](https://www.courier.com/docs/reference/get-started/)
48
+ - [RubyDoc](https://gemdocs.org/gems/trycourier)
49
+ <!-- AUTO-GENERATED-OVERVIEW:END -->
@@ -33,6 +33,9 @@ module Courier
33
33
  # @return [Courier::Resources::Automations]
34
34
  attr_reader :automations
35
35
 
36
+ # @return [Courier::Resources::Journeys]
37
+ attr_reader :journeys
38
+
36
39
  # @return [Courier::Resources::Brands]
37
40
  attr_reader :brands
38
41
 
@@ -118,6 +121,7 @@ module Courier
118
121
  @audit_events = Courier::Resources::AuditEvents.new(client: self)
119
122
  @auth = Courier::Resources::Auth.new(client: self)
120
123
  @automations = Courier::Resources::Automations.new(client: self)
124
+ @journeys = Courier::Resources::Journeys.new(client: self)
121
125
  @brands = Courier::Resources::Brands.new(client: self)
122
126
  @bulk = Courier::Resources::Bulk.new(client: self)
123
127
  @inbound = Courier::Resources::Inbound.new(client: self)
@@ -490,6 +490,37 @@ module Courier
490
490
  JSONL_CONTENT = %r{^application/(:?x-(?:n|l)djson)|(:?(?:x-)?jsonl)}
491
491
 
492
492
  class << self
493
+ # @api private
494
+ #
495
+ # @param query [Hash{Symbol=>Object}]
496
+ #
497
+ # @return [Hash{Symbol=>Object}]
498
+ def encode_query_params(query)
499
+ out = {}
500
+ query.each { write_query_param_element!(out, _1, _2) }
501
+ out
502
+ end
503
+
504
+ # @api private
505
+ #
506
+ # @param collection [Hash{Symbol=>Object}]
507
+ # @param key [String]
508
+ # @param element [Object]
509
+ #
510
+ # @return [nil]
511
+ private def write_query_param_element!(collection, key, element)
512
+ case element
513
+ in Hash
514
+ element.each do |name, value|
515
+ write_query_param_element!(collection, "#{key}[#{name}]", value)
516
+ end
517
+ in Array
518
+ collection[key] = element.map(&:to_s).join(",")
519
+ else
520
+ collection[key] = element.to_s
521
+ end
522
+ end
523
+
493
524
  # @api private
494
525
  #
495
526
  # @param y [Enumerator::Yielder]
@@ -7,7 +7,13 @@ module Courier
7
7
  extend Courier::Internal::Type::RequestParameters::Converter
8
8
  include Courier::Internal::Type::RequestParameters
9
9
 
10
- # @!method initialize(request_options: {})
10
+ # @!attribute audience_id
11
+ #
12
+ # @return [String]
13
+ required :audience_id, String
14
+
15
+ # @!method initialize(audience_id:, request_options: {})
16
+ # @param audience_id [String]
11
17
  # @param request_options [Courier::RequestOptions, Hash{Symbol=>Object}]
12
18
  end
13
19
  end
@@ -7,13 +7,20 @@ module Courier
7
7
  extend Courier::Internal::Type::RequestParameters::Converter
8
8
  include Courier::Internal::Type::RequestParameters
9
9
 
10
+ # @!attribute audience_id
11
+ #
12
+ # @return [String]
13
+ required :audience_id, String
14
+
10
15
  # @!attribute cursor
11
16
  # A unique identifier that allows for fetching the next set of members
12
17
  #
13
18
  # @return [String, nil]
14
19
  optional :cursor, String, nil?: true
15
20
 
16
- # @!method initialize(cursor: nil, request_options: {})
21
+ # @!method initialize(audience_id:, cursor: nil, request_options: {})
22
+ # @param audience_id [String]
23
+ #
17
24
  # @param cursor [String, nil] A unique identifier that allows for fetching the next set of members
18
25
  #
19
26
  # @param request_options [Courier::RequestOptions, Hash{Symbol=>Object}]
@@ -7,7 +7,13 @@ module Courier
7
7
  extend Courier::Internal::Type::RequestParameters::Converter
8
8
  include Courier::Internal::Type::RequestParameters
9
9
 
10
- # @!method initialize(request_options: {})
10
+ # @!attribute audience_id
11
+ #
12
+ # @return [String]
13
+ required :audience_id, String
14
+
15
+ # @!method initialize(audience_id:, request_options: {})
16
+ # @param audience_id [String]
11
17
  # @param request_options [Courier::RequestOptions, Hash{Symbol=>Object}]
12
18
  end
13
19
  end
@@ -7,6 +7,11 @@ module Courier
7
7
  extend Courier::Internal::Type::RequestParameters::Converter
8
8
  include Courier::Internal::Type::RequestParameters
9
9
 
10
+ # @!attribute audience_id
11
+ #
12
+ # @return [String]
13
+ required :audience_id, String
14
+
10
15
  # @!attribute description
11
16
  # A description of the audience
12
17
  #
@@ -31,10 +36,12 @@ module Courier
31
36
  # @return [Symbol, Courier::Models::AudienceUpdateParams::Operator, nil]
32
37
  optional :operator, enum: -> { Courier::AudienceUpdateParams::Operator }, nil?: true
33
38
 
34
- # @!method initialize(description: nil, filter: nil, name: nil, operator: nil, request_options: {})
39
+ # @!method initialize(audience_id:, description: nil, filter: nil, name: nil, operator: nil, request_options: {})
35
40
  # Some parameter documentations has been truncated, see
36
41
  # {Courier::Models::AudienceUpdateParams} for more details.
37
42
  #
43
+ # @param audience_id [String]
44
+ #
38
45
  # @param description [String, nil] A description of the audience
39
46
  #
40
47
  # @param filter [Courier::Models::AudienceFilterConfig, nil] Filter configuration for audience membership containing an array of filter rules
@@ -7,7 +7,13 @@ module Courier
7
7
  extend Courier::Internal::Type::RequestParameters::Converter
8
8
  include Courier::Internal::Type::RequestParameters
9
9
 
10
- # @!method initialize(request_options: {})
10
+ # @!attribute audit_event_id
11
+ #
12
+ # @return [String]
13
+ required :audit_event_id, String
14
+
15
+ # @!method initialize(audit_event_id:, request_options: {})
16
+ # @param audit_event_id [String]
11
17
  # @param request_options [Courier::RequestOptions, Hash{Symbol=>Object}]
12
18
  end
13
19
  end
@@ -8,6 +8,11 @@ module Courier
8
8
  extend Courier::Internal::Type::RequestParameters::Converter
9
9
  include Courier::Internal::Type::RequestParameters
10
10
 
11
+ # @!attribute template_id
12
+ #
13
+ # @return [String]
14
+ required :template_id, String
15
+
11
16
  # @!attribute recipient
12
17
  #
13
18
  # @return [String, nil]
@@ -33,7 +38,8 @@ module Courier
33
38
  # @return [String, nil]
34
39
  optional :template, String, nil?: true
35
40
 
36
- # @!method initialize(recipient:, brand: nil, data: nil, profile: nil, template: nil, request_options: {})
41
+ # @!method initialize(template_id:, recipient:, brand: nil, data: nil, profile: nil, template: nil, request_options: {})
42
+ # @param template_id [String]
37
43
  # @param recipient [String, nil]
38
44
  # @param brand [String, nil]
39
45
  # @param data [Hash{Symbol=>Object}, nil]
@@ -7,7 +7,13 @@ module Courier
7
7
  extend Courier::Internal::Type::RequestParameters::Converter
8
8
  include Courier::Internal::Type::RequestParameters
9
9
 
10
- # @!method initialize(request_options: {})
10
+ # @!attribute brand_id
11
+ #
12
+ # @return [String]
13
+ required :brand_id, String
14
+
15
+ # @!method initialize(brand_id:, request_options: {})
16
+ # @param brand_id [String]
11
17
  # @param request_options [Courier::RequestOptions, Hash{Symbol=>Object}]
12
18
  end
13
19
  end
@@ -7,7 +7,13 @@ module Courier
7
7
  extend Courier::Internal::Type::RequestParameters::Converter
8
8
  include Courier::Internal::Type::RequestParameters
9
9
 
10
- # @!method initialize(request_options: {})
10
+ # @!attribute brand_id
11
+ #
12
+ # @return [String]
13
+ required :brand_id, String
14
+
15
+ # @!method initialize(brand_id:, request_options: {})
16
+ # @param brand_id [String]
11
17
  # @param request_options [Courier::RequestOptions, Hash{Symbol=>Object}]
12
18
  end
13
19
  end
@@ -7,6 +7,11 @@ module Courier
7
7
  extend Courier::Internal::Type::RequestParameters::Converter
8
8
  include Courier::Internal::Type::RequestParameters
9
9
 
10
+ # @!attribute brand_id
11
+ #
12
+ # @return [String]
13
+ required :brand_id, String
14
+
10
15
  # @!attribute name
11
16
  # The name of the brand.
12
17
  #
@@ -23,7 +28,9 @@ module Courier
23
28
  # @return [Courier::Models::BrandSnippets, nil]
24
29
  optional :snippets, -> { Courier::BrandSnippets }, nil?: true
25
30
 
26
- # @!method initialize(name:, settings: nil, snippets: nil, request_options: {})
31
+ # @!method initialize(brand_id:, name:, settings: nil, snippets: nil, request_options: {})
32
+ # @param brand_id [String]
33
+ #
27
34
  # @param name [String] The name of the brand.
28
35
  #
29
36
  # @param settings [Courier::Models::BrandSettings, nil]
@@ -7,12 +7,18 @@ module Courier
7
7
  extend Courier::Internal::Type::RequestParameters::Converter
8
8
  include Courier::Internal::Type::RequestParameters
9
9
 
10
+ # @!attribute job_id
11
+ #
12
+ # @return [String]
13
+ required :job_id, String
14
+
10
15
  # @!attribute users
11
16
  #
12
17
  # @return [Array<Courier::Models::InboundBulkMessageUser>]
13
18
  required :users, -> { Courier::Internal::Type::ArrayOf[Courier::InboundBulkMessageUser] }
14
19
 
15
- # @!method initialize(users:, request_options: {})
20
+ # @!method initialize(job_id:, users:, request_options: {})
21
+ # @param job_id [String]
16
22
  # @param users [Array<Courier::Models::InboundBulkMessageUser>]
17
23
  # @param request_options [Courier::RequestOptions, Hash{Symbol=>Object}]
18
24
  end
@@ -7,6 +7,11 @@ module Courier
7
7
  extend Courier::Internal::Type::RequestParameters::Converter
8
8
  include Courier::Internal::Type::RequestParameters
9
9
 
10
+ # @!attribute job_id
11
+ #
12
+ # @return [String]
13
+ required :job_id, String
14
+
10
15
  # @!attribute cursor
11
16
  # A unique identifier that allows for fetching the next set of users added to the
12
17
  # bulk job
@@ -14,10 +19,12 @@ module Courier
14
19
  # @return [String, nil]
15
20
  optional :cursor, String, nil?: true
16
21
 
17
- # @!method initialize(cursor: nil, request_options: {})
22
+ # @!method initialize(job_id:, cursor: nil, request_options: {})
18
23
  # Some parameter documentations has been truncated, see
19
24
  # {Courier::Models::BulkListUsersParams} for more details.
20
25
  #
26
+ # @param job_id [String]
27
+ #
21
28
  # @param cursor [String, nil] A unique identifier that allows for fetching the next set of users added to the
22
29
  #
23
30
  # @param request_options [Courier::RequestOptions, Hash{Symbol=>Object}]
@@ -7,7 +7,13 @@ module Courier
7
7
  extend Courier::Internal::Type::RequestParameters::Converter
8
8
  include Courier::Internal::Type::RequestParameters
9
9
 
10
- # @!method initialize(request_options: {})
10
+ # @!attribute job_id
11
+ #
12
+ # @return [String]
13
+ required :job_id, String
14
+
15
+ # @!method initialize(job_id:, request_options: {})
16
+ # @param job_id [String]
11
17
  # @param request_options [Courier::RequestOptions, Hash{Symbol=>Object}]
12
18
  end
13
19
  end
@@ -7,7 +7,13 @@ module Courier
7
7
  extend Courier::Internal::Type::RequestParameters::Converter
8
8
  include Courier::Internal::Type::RequestParameters
9
9
 
10
- # @!method initialize(request_options: {})
10
+ # @!attribute job_id
11
+ #
12
+ # @return [String]
13
+ required :job_id, String
14
+
15
+ # @!method initialize(job_id:, request_options: {})
16
+ # @param job_id [String]
11
17
  # @param request_options [Courier::RequestOptions, Hash{Symbol=>Object}]
12
18
  end
13
19
  end
@@ -14,17 +14,10 @@ module Courier
14
14
  # @return [String]
15
15
  required :version, String
16
16
 
17
- # @!attribute brand
18
- #
19
- # @return [String, nil]
20
- optional :brand, String, nil?: true
21
-
22
- # @!method initialize(elements:, version:, brand: nil)
17
+ # @!method initialize(elements:, version:)
23
18
  # @param elements [Array<Courier::Models::ElementalTextNodeWithType, Courier::Models::ElementalMetaNodeWithType, Courier::Models::ElementalChannelNodeWithType, Courier::Models::ElementalImageNodeWithType, Courier::Models::ElementalActionNodeWithType, Courier::Models::ElementalDividerNodeWithType, Courier::Models::ElementalQuoteNodeWithType>]
24
19
  #
25
20
  # @param version [String] For example, "2022-01-01"
26
- #
27
- # @param brand [String, nil]
28
21
  end
29
22
  end
30
23
  end