loops_sdk 2.0.0 → 2.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aeb0e30d4d199706208e701f31340143aa7eae407b04f561b6bc14fa94f05346
4
- data.tar.gz: f48c5dc9e2e5c69314656fc20f0e49402ad803b57a01cb00ad9079a837601ce8
3
+ metadata.gz: b5f428ed70457f4a5545ae8db80c8c1184053851702186b93e4181de19826bac
4
+ data.tar.gz: 273c41adcdfc013281b38e6e7fbf9d1d5f754e3e0e34979b660284689c26d9a6
5
5
  SHA512:
6
- metadata.gz: 526bec40bd9353f5c7c62adc9c049e70cee26eead9d81619ffe9933f5054aa4d416447a18b5d5aea966cf795784b3bf0446d132fa5c2963e16e1a975cffc5d8a
7
- data.tar.gz: d178e957e3b3287ce91e2598d2e871fb91e4075e9d7dbd8a6002d5d4a11cb91b11e0355c031a38e71ab18c7093320ec4d8619792e4aa628411347f3d1d5b87a3
6
+ metadata.gz: cf08a088e5a500fe82ff04a7e432c4bae832a9414ba693c69f9d13ba381a85dcad63e554b09449f3c2f79c1e150a7309f5071b98c4a5a022820f044e93e8216b
7
+ data.tar.gz: fa693592b67828b4c1a351a938f2c16bc058f29830f442cab2d009383b9e07a108df1c1fb836c54066c067d0bce222a26f975994b16d1d3348a91780fc783fd5
data/README.md CHANGED
@@ -95,6 +95,7 @@ Each contact in Loops has a set of default properties. These will always be retu
95
95
  - `subscribed`
96
96
  - `userGroup`
97
97
  - `userId`
98
+ - `optInStatus`
98
99
 
99
100
  ## Custom contact properties
100
101
 
@@ -107,12 +108,25 @@ You can use custom contact properties in API calls. Please make sure to [add cus
107
108
  - [Contacts.update()](#contactsupdate)
108
109
  - [Contacts.find()](#contactsfind)
109
110
  - [Contacts.delete()](#contactsdelete)
111
+ - [Contacts.check_suppression()](#contactscheck_suppression)
112
+ - [Contacts.remove_suppression()](#contactsremove_suppression)
110
113
  - [ContactProperties.create()](#contactpropertiescreate)
111
114
  - [ContactProperties.list()](#contactpropertieslist)
112
115
  - [MailingLists.list()](#mailinglistslist)
113
116
  - [Events.send()](#eventssend)
114
117
  - [Transactional.send()](#transactionalsend)
115
118
  - [Transactional.list()](#transactionallist)
119
+ - [DedicatedSendingIps.list()](#dedicatedsendingipslist)
120
+ - [Themes.list()](#themeslist)
121
+ - [Themes.get()](#themesget)
122
+ - [Components.list()](#componentslist)
123
+ - [Components.get()](#componentsget)
124
+ - [Campaigns.list()](#campaignslist)
125
+ - [Campaigns.create()](#campaignscreate)
126
+ - [Campaigns.get()](#campaignsget)
127
+ - [Campaigns.update()](#campaignsupdate)
128
+ - [EmailMessages.get()](#emailmessagesget)
129
+ - [EmailMessages.update()](#emailmessagesupdate)
116
130
 
117
131
  ---
118
132
 
@@ -209,7 +223,7 @@ This method will return a success or error message:
209
223
 
210
224
  Update a contact. This method will create a contact if one doesn't already exist.
211
225
 
212
- Note: To update a contact's email address, the contact requires a `userId` value. Then you can make a request with their `userId` and an updated email address.
226
+ Note: To update a contact's email address, the contact requires a `user_id` value. Then you can make a request with their `user_id` and an updated email address.
213
227
 
214
228
  [API Reference](https://loops.so/docs/api-reference/update-contact)
215
229
 
@@ -234,12 +248,10 @@ response = LoopsSdk::Contacts.update(
234
248
  properties: contact_properties
235
249
  )
236
250
 
237
- # Updating a contact's email address using userId
251
+ # Updating a contact's email address using user_id
238
252
  response = LoopsSdk::Contacts.update(
239
253
  email: "newemail@gmail.com",
240
- properties: {
241
- userId: "1234",
242
- }
254
+ user_id: "1234"
243
255
  )
244
256
 
245
257
  # Subscribing a contact to a mailing list
@@ -314,6 +326,7 @@ If no contact is found, an empty list will be returned.
314
326
  "mailingLists": {
315
327
  "cm06f5v0e45nf0ml5754o9cix": true
316
328
  },
329
+ "optInStatus": null,
317
330
  "favoriteColor": "Blue" /* Custom property */
318
331
  }
319
332
  ]
@@ -364,6 +377,104 @@ This method will return a success or error message:
364
377
 
365
378
  ---
366
379
 
380
+ ### Contacts.check_suppression()
381
+
382
+ Check if a contact is suppressed.
383
+
384
+ [API Reference](https://loops.so/docs/api-reference/check-contact-suppression)
385
+
386
+ #### Parameters
387
+
388
+ You must use one parameter in the request.
389
+
390
+ | Name | Type | Required | Notes |
391
+ | --------- | ------ | -------- | ----- |
392
+ | `email` | string | No | |
393
+ | `user_id` | string | No | |
394
+
395
+ #### Example
396
+
397
+ ```ruby
398
+ response = LoopsSdk::Contacts.check_suppression(email: "hello@gmail.com")
399
+
400
+ response = LoopsSdk::Contacts.check_suppression(user_id: "12345")
401
+ ```
402
+
403
+ #### Response
404
+
405
+ This method will return the suppression status for a contact and the suppression removal quota.
406
+
407
+ ```json
408
+ {
409
+ "contact": {
410
+ "id": "cll6b3i8901a9jx0oyktl2m4u",
411
+ "email": "hello@gmail.com",
412
+ "userId": "12345"
413
+ },
414
+ "isSuppressed": true,
415
+ "removalQuota": {
416
+ "limit": 100,
417
+ "remaining": 4
418
+ }
419
+ }
420
+ ```
421
+
422
+ ```json
423
+ {
424
+ "success": false,
425
+ "message": "An email or userId is required."
426
+ }
427
+ ```
428
+
429
+ ---
430
+
431
+ ### Contacts.remove_suppression()
432
+
433
+ Remove suppression for a contact.
434
+
435
+ [API Reference](https://loops.so/docs/api-reference/remove-contact-suppression)
436
+
437
+ #### Parameters
438
+
439
+ You must use one parameter in the request.
440
+
441
+ | Name | Type | Required | Notes |
442
+ | --------- | ------ | -------- | ----- |
443
+ | `email` | string | No | |
444
+ | `user_id` | string | No | |
445
+
446
+ #### Example
447
+
448
+ ```ruby
449
+ response = LoopsSdk::Contacts.remove_suppression(email: "hello@gmail.com")
450
+
451
+ response = LoopsSdk::Contacts.remove_suppression(user_id: "12345")
452
+ ```
453
+
454
+ #### Response
455
+
456
+ This method will return a success or error message:
457
+
458
+ ```json
459
+ {
460
+ "success": true,
461
+ "message": "Email removed from suppression list.",
462
+ "removalQuota": {
463
+ "limit": 100,
464
+ "remaining": 4
465
+ }
466
+ }
467
+ ```
468
+
469
+ ```json
470
+ {
471
+ "success": false,
472
+ "message": "This contact is not suppressed."
473
+ }
474
+ ```
475
+
476
+ ---
477
+
367
478
  ### ContactProperties.create()
368
479
 
369
480
  Create a new contact property.
@@ -770,6 +881,256 @@ response = LoopsSdk::Transactional.list(perPage: 15)
770
881
 
771
882
  ---
772
883
 
884
+ ### DedicatedSendingIps.list()
885
+
886
+ Get Loops' dedicated sending IP addresses.
887
+
888
+ [API Reference](https://loops.so/docs/api-reference/list-dedicated-sending-ips)
889
+
890
+ #### Parameters
891
+
892
+ None
893
+
894
+ #### Example
895
+
896
+ ```ruby
897
+ response = LoopsSdk::DedicatedSendingIps.list
898
+ ```
899
+
900
+ #### Response
901
+
902
+ Returns an array of IP address strings.
903
+
904
+ ```json
905
+ ["1.2.3.4", "5.6.7.8"]
906
+ ```
907
+
908
+ ---
909
+
910
+ ### Themes.list()
911
+
912
+ List email themes.
913
+
914
+ [API Reference](https://loops.so/docs/api-reference/list-themes)
915
+
916
+ #### Parameters
917
+
918
+ | Name | Type | Required | Notes |
919
+ | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
920
+ | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
921
+ | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
922
+
923
+ #### Example
924
+
925
+ ```ruby
926
+ response = LoopsSdk::Themes.list
927
+
928
+ response = LoopsSdk::Themes.list(perPage: 15, cursor: "cursor_value")
929
+ ```
930
+
931
+ ---
932
+
933
+ ### Themes.get()
934
+
935
+ Get a single theme by ID.
936
+
937
+ [API Reference](https://loops.so/docs/api-reference/get-theme)
938
+
939
+ #### Parameters
940
+
941
+ | Name | Type | Required | Notes |
942
+ | ---------- | ------ | -------- | ----- |
943
+ | `theme_id` | string | Yes | |
944
+
945
+ #### Example
946
+
947
+ ```ruby
948
+ response = LoopsSdk::Themes.get(theme_id: "theme_123")
949
+ ```
950
+
951
+ ---
952
+
953
+ ### Components.list()
954
+
955
+ List email components.
956
+
957
+ [API Reference](https://loops.so/docs/api-reference/list-components)
958
+
959
+ #### Parameters
960
+
961
+ | Name | Type | Required | Notes |
962
+ | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
963
+ | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
964
+ | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
965
+
966
+ #### Example
967
+
968
+ ```ruby
969
+ response = LoopsSdk::Components.list
970
+ ```
971
+
972
+ ---
973
+
974
+ ### Components.get()
975
+
976
+ Get a single component by ID.
977
+
978
+ [API Reference](https://loops.so/docs/api-reference/get-component)
979
+
980
+ #### Parameters
981
+
982
+ | Name | Type | Required | Notes |
983
+ | -------------- | ------ | -------- | ----- |
984
+ | `component_id` | string | Yes | |
985
+
986
+ #### Example
987
+
988
+ ```ruby
989
+ response = LoopsSdk::Components.get(component_id: "component_123")
990
+ ```
991
+
992
+ ---
993
+
994
+ ### Campaigns.list()
995
+
996
+ List campaigns.
997
+
998
+ [API Reference](https://loops.so/docs/api-reference/list-campaigns)
999
+
1000
+ #### Parameters
1001
+
1002
+ | Name | Type | Required | Notes |
1003
+ | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
1004
+ | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
1005
+ | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
1006
+
1007
+ #### Example
1008
+
1009
+ ```ruby
1010
+ response = LoopsSdk::Campaigns.list
1011
+ ```
1012
+
1013
+ ---
1014
+
1015
+ ### Campaigns.create()
1016
+
1017
+ Create a draft campaign. An empty email message is created automatically.
1018
+
1019
+ [API Reference](https://loops.so/docs/api-reference/create-campaign)
1020
+
1021
+ #### Parameters
1022
+
1023
+ | Name | Type | Required | Notes |
1024
+ | ------ | ------ | -------- | ----- |
1025
+ | `name` | string | Yes | |
1026
+
1027
+ #### Example
1028
+
1029
+ ```ruby
1030
+ response = LoopsSdk::Campaigns.create(name: "Spring announcement")
1031
+ ```
1032
+
1033
+ ---
1034
+
1035
+ ### Campaigns.get()
1036
+
1037
+ Get a single campaign by ID.
1038
+
1039
+ [API Reference](https://loops.so/docs/api-reference/get-campaign)
1040
+
1041
+ #### Parameters
1042
+
1043
+ | Name | Type | Required | Notes |
1044
+ | ------------- | ------ | -------- | ----- |
1045
+ | `campaign_id` | string | Yes | |
1046
+
1047
+ #### Example
1048
+
1049
+ ```ruby
1050
+ response = LoopsSdk::Campaigns.get(campaign_id: "campaign_123")
1051
+ ```
1052
+
1053
+ ---
1054
+
1055
+ ### Campaigns.update()
1056
+
1057
+ Update a draft campaign's name.
1058
+
1059
+ [API Reference](https://loops.so/docs/api-reference/update-campaign)
1060
+
1061
+ #### Parameters
1062
+
1063
+ | Name | Type | Required | Notes |
1064
+ | ------------- | ------ | -------- | ----- |
1065
+ | `campaign_id` | string | Yes | |
1066
+ | `name` | string | Yes | |
1067
+
1068
+ #### Example
1069
+
1070
+ ```ruby
1071
+ response = LoopsSdk::Campaigns.update(
1072
+ campaign_id: "campaign_123",
1073
+ name: "Updated campaign name"
1074
+ )
1075
+ ```
1076
+
1077
+ ---
1078
+
1079
+ ### EmailMessages.get()
1080
+
1081
+ Get an email message, including its LMX content.
1082
+
1083
+ [API Reference](https://loops.so/docs/api-reference/get-email-message)
1084
+
1085
+ #### Parameters
1086
+
1087
+ | Name | Type | Required | Notes |
1088
+ | ------------------- | ------ | -------- | ----- |
1089
+ | `email_message_id` | string | Yes | |
1090
+
1091
+ #### Example
1092
+
1093
+ ```ruby
1094
+ response = LoopsSdk::EmailMessages.get(email_message_id: "message_123")
1095
+ ```
1096
+
1097
+ ---
1098
+
1099
+ ### EmailMessages.update()
1100
+
1101
+ Update an email message for a draft campaign.
1102
+
1103
+ [API Reference](https://loops.so/docs/api-reference/update-email-message)
1104
+
1105
+ #### Parameters
1106
+
1107
+ | Name | Type | Required | Notes |
1108
+ | ------------------------ | ------ | -------- | ---------------------------------------------------------------------------------------------------------- |
1109
+ | `email_message_id` | string | Yes | |
1110
+ | `expected_revision_id` | string | No | The `contentRevisionId` from your last fetch. Required to avoid stale concurrent updates. |
1111
+ | `subject` | string | No | |
1112
+ | `preview_text` | string | No | |
1113
+ | `from_name` | string | No | |
1114
+ | `from_email` | string | No | Sender username without `@` or domain. The team's sending domain is appended automatically. |
1115
+ | `reply_to_email` | string | No | Must be empty or a valid email address. |
1116
+ | `lmx` | string | No | Email body serialized as LMX. Styles must be embedded in the LMX `<Style />` tag. |
1117
+
1118
+ #### Example
1119
+
1120
+ ```ruby
1121
+ response = LoopsSdk::EmailMessages.update(
1122
+ email_message_id: "message_123",
1123
+ expected_revision_id: "revision_123",
1124
+ subject: "Spring announcement",
1125
+ preview_text: "See what's new",
1126
+ from_name: "Loops",
1127
+ from_email: "hello",
1128
+ lmx: "<Email><Style /></Email>"
1129
+ )
1130
+ ```
1131
+
1132
+ ---
1133
+
773
1134
  ## Testing
774
1135
 
775
1136
  Run tests with `bundle exec rspec`.
@@ -7,13 +7,13 @@ module LoopsSdk
7
7
 
8
8
  def handle_response(response)
9
9
  case response.status
10
- when 200
10
+ when 200, 201
11
11
  JSON.parse(response.body)
12
12
  when 429
13
13
  limit = response.headers["x-ratelimit-limit"]
14
14
  remaining = response.headers["x-ratelimit-remaining"]
15
15
  raise RateLimitError.new(limit, remaining)
16
- when 400, 404, 405, 409, 500
16
+ when 400, 401, 404, 405, 409, 413, 422, 500
17
17
  raise APIError.new(response.status, response.body)
18
18
  else
19
19
  raise APIError.new(response.status, "Unexpected error occurred")
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LoopsSdk
4
+ class Campaigns < Base
5
+ class << self
6
+ def list(perPage: 20, cursor: nil)
7
+ make_request(method: :get, path: "v1/campaigns", params: { perPage: perPage, cursor: cursor })
8
+ end
9
+
10
+ def create(name:)
11
+ make_request(method: :post, path: "v1/campaigns", body: { name: name })
12
+ end
13
+
14
+ def get(campaign_id:)
15
+ make_request(method: :get, path: "v1/campaigns/#{campaign_id}")
16
+ end
17
+
18
+ def update(campaign_id:, name:)
19
+ make_request(method: :post, path: "v1/campaigns/#{campaign_id}", body: { name: name })
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LoopsSdk
4
+ class Components < Base
5
+ class << self
6
+ def list(perPage: 20, cursor: nil)
7
+ make_request(method: :get, path: "v1/components", params: { perPage: perPage, cursor: cursor })
8
+ end
9
+
10
+ def get(component_id:)
11
+ make_request(method: :get, path: "v1/components/#{component_id}")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -37,6 +37,22 @@ module LoopsSdk
37
37
  body = email ? { email: email } : { userId: user_id }
38
38
  make_request(method: :post, path: "v1/contacts/delete", body: body)
39
39
  end
40
+
41
+ def check_suppression(email: nil, user_id: nil)
42
+ raise ArgumentError, "Only one parameter is permitted." if email && user_id
43
+ raise ArgumentError, "You must provide an email or user_id value." if email.nil? && user_id.nil?
44
+
45
+ params = email ? { email: email } : { userId: user_id }
46
+ make_request(method: :get, path: "v1/contacts/suppression", params: params)
47
+ end
48
+
49
+ def remove_suppression(email: nil, user_id: nil)
50
+ raise ArgumentError, "Only one parameter is permitted." if email && user_id
51
+ raise ArgumentError, "You must provide an email or user_id value." if email.nil? && user_id.nil?
52
+
53
+ params = email ? { email: email } : { userId: user_id }
54
+ make_request(method: :delete, path: "v1/contacts/suppression", params: params)
55
+ end
40
56
  end
41
57
  end
42
58
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LoopsSdk
4
+ class DedicatedSendingIps < Base
5
+ class << self
6
+ def list
7
+ make_request(method: :get, path: "v1/dedicated-sending-ips")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LoopsSdk
4
+ class EmailMessages < Base
5
+ class << self
6
+ def get(email_message_id:)
7
+ make_request(method: :get, path: "v1/email-messages/#{email_message_id}")
8
+ end
9
+
10
+ def update(email_message_id:, expected_revision_id: nil, subject: nil, preview_text: nil, from_name: nil, from_email: nil, reply_to_email: nil, lmx: nil)
11
+ body = {
12
+ expectedRevisionId: expected_revision_id,
13
+ subject: subject,
14
+ previewText: preview_text,
15
+ fromName: from_name,
16
+ fromEmail: from_email,
17
+ replyToEmail: reply_to_email,
18
+ lmx: lmx
19
+ }.compact
20
+ make_request(method: :post, path: "v1/email-messages/#{email_message_id}", body: body)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LoopsSdk
4
+ class Themes < Base
5
+ class << self
6
+ def list(perPage: 20, cursor: nil)
7
+ make_request(method: :get, path: "v1/themes", params: { perPage: perPage, cursor: cursor })
8
+ end
9
+
10
+ def get(theme_id:)
11
+ make_request(method: :get, path: "v1/themes/#{theme_id}")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LoopsSdk
4
- VERSION = "2.0.0"
4
+ VERSION = "2.2.0"
5
5
  end
data/lib/loops_sdk.rb CHANGED
@@ -11,6 +11,11 @@ require_relative "loops_sdk/mailing_lists"
11
11
  require_relative "loops_sdk/transactional"
12
12
  require_relative "loops_sdk/contact_properties"
13
13
  require_relative "loops_sdk/api_key"
14
+ require_relative "loops_sdk/dedicated_sending_ips"
15
+ require_relative "loops_sdk/themes"
16
+ require_relative "loops_sdk/components"
17
+ require_relative "loops_sdk/campaigns"
18
+ require_relative "loops_sdk/email_messages"
14
19
 
15
20
  module LoopsSdk
16
21
  class << self
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: loops_sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Rowden
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-08-22 00:00:00.000000000 Z
11
+ date: 2026-05-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -36,11 +36,16 @@ files:
36
36
  - lib/loops_sdk.rb
37
37
  - lib/loops_sdk/api_key.rb
38
38
  - lib/loops_sdk/base.rb
39
+ - lib/loops_sdk/campaigns.rb
40
+ - lib/loops_sdk/components.rb
39
41
  - lib/loops_sdk/configuration.rb
40
42
  - lib/loops_sdk/contact_properties.rb
41
43
  - lib/loops_sdk/contacts.rb
44
+ - lib/loops_sdk/dedicated_sending_ips.rb
45
+ - lib/loops_sdk/email_messages.rb
42
46
  - lib/loops_sdk/events.rb
43
47
  - lib/loops_sdk/mailing_lists.rb
48
+ - lib/loops_sdk/themes.rb
44
49
  - lib/loops_sdk/transactional.rb
45
50
  - lib/loops_sdk/version.rb
46
51
  homepage: https://loops.so/docs/api-reference
@@ -65,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
65
70
  - !ruby/object:Gem::Version
66
71
  version: '0'
67
72
  requirements: []
68
- rubygems_version: 3.5.11
73
+ rubygems_version: 3.4.10
69
74
  signing_key:
70
75
  specification_version: 4
71
76
  summary: The official Ruby SDK for Loops.