millionsend 0.6.0 → 0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d6e46f436a0805ef763edac14bbd254e6f17503c598a8374b6abe9f45efcc76
4
- data.tar.gz: 04c8ab67229c03c8769c7f9ef872b0e11b98d8aa8919a21c0d4c6ce6cc0582f9
3
+ metadata.gz: e916d31911bcbb75dc0c6aae5f671a2034a6aff4eac3f0b9be4e9eaa58b5c747
4
+ data.tar.gz: ba3d5063d02bd243660dfe04dee0780a84b2bad05cd11cd2c2690f3ffcfeca2a
5
5
  SHA512:
6
- metadata.gz: ea4ff1283155e92f65aea2088a6ae4abdfd5032b3c93c243eb838a30ab141070c328bc7d2a278e0399894ec1e18300abef83a727ace9ed48a4b7e3271941e284
7
- data.tar.gz: e845a9d24731c584b931a0de44fd7f0adee3760bcaa8d45d55412e301c6e9ad545cc1d62e294938e8adbdba07ebc204d156a96584d2d5b16fc721826853137dc
6
+ metadata.gz: 5c5d558bf5d08c705e8ae11ba6a6ed5e7cf6220a9629eb5a83bcc5570a713e669b5a7718b6d0f10b8bef00c5bf730d1e89599f6072948d76931cffe675b1d46c
7
+ data.tar.gz: 65edabd5b84e8f224045a7ebaab207f962221a75aab27ca32330435bee190a15834c45a06c7fcaeeeda1a77c46c305be4a314304d256968657d4c1886ad12ef3
data/README.md CHANGED
@@ -121,8 +121,13 @@ contact = Millionsend::Contacts.create(
121
121
  )
122
122
  Millionsend::Contacts.get("ada@acme.dev") # by id or email; also resend-ruby's get(id: ...) / get(email: ...)
123
123
  Millionsend::Contacts.update(id: contact[:id], unsubscribed: true, first_name: nil) # nil clears
124
- Millionsend::Contacts.remove("ada@acme.dev")
124
+ Millionsend::Contacts.remove("ada@acme.dev") # the contact's emails stay in the send log
125
+ Millionsend::Contacts.remove("ada@acme.dev", erase: true) # ?erase=true — also scrubs the address from email
126
+ # history, event payloads and API logs (GDPR/LGPD)
125
127
  Millionsend::Contacts.list(limit: 50)
128
+ # Bulk read (MillionSend extension): carry the property map and the topic subscriptions on every
129
+ # item, so an audience reads in one request per 100 contacts instead of one per contact
130
+ Millionsend::Contacts.list(limit: 100, include: ["properties", "topics"]) # ?include=properties,topics
126
131
 
127
132
  # Topic subscriptions (granular unsubscribe) — PATCH /contacts/:id/topics
128
133
  Millionsend::Contacts::Topics.update(email: "ada@acme.dev", topics: [{ id: topic_id, subscription: "opt_out" }]) # resend-ruby shape
@@ -149,8 +154,15 @@ result[:data] # [{ index:, id:, status: "created" | "updated" | "skipped" }]
149
154
  result[:counts] # { created:, updated:, skipped:, failed: }
150
155
  result[:errors] # permissive mode only: [{ index:, message: }]
151
156
 
157
+ # Bulk lookup (MillionSend extension) — up to 1000 contacts by id or email in one request, in
158
+ # request order; unknown entries are listed, not errors — one request against the rate limit
159
+ found = Millionsend::Contacts::Batch.get([contact[:id], "b@acme.dev", { email: "c@acme.dev" }], include: ["topics"])
160
+ found[:data] # [{ object: "contact", id:, email:, first_name:, last_name:, created_at:, unsubscribed:, topics: }]
161
+ found[:missing] # [{ index:, email: }] / [{ index:, id: }] — request entries that matched nobody
162
+
152
163
  # Bulk delete (MillionSend extension) — up to 1000 per call, exactly one of ids: / emails:
153
- Millionsend::Contacts::Batch.remove(emails: ["a@acme.dev", "b@acme.dev"]) # or ids: [...]
164
+ Millionsend::Contacts::Batch.remove(emails: ["a@acme.dev", "b@acme.dev"]) # or ids: [...]; emails stay in the log
165
+ Millionsend::Contacts::Batch.remove(ids: [contact[:id]], erase: true) # also scrubs each address, as Contacts.remove
154
166
  # => { data: [{ object: "contact", contact: "<uuid>", deleted: true }, ...] } — only the rows actually deleted
155
167
  ```
156
168
 
@@ -307,6 +319,7 @@ segment = Millionsend::Segments.create(
307
319
  Millionsend::Segments.get(segment[:id]) # includes a live contact_count
308
320
  Millionsend::Segments.list
309
321
  Millionsend::Segments.contacts(segment[:id], limit: 50) # the contacts currently matching
322
+ Millionsend::Segments.contacts(segment[:id], include: ["properties", "topics"]) # with each contact's extras, as Contacts.list
310
323
  Millionsend::Segments.update(segment[:id], name: "Pro tier")
311
324
  Millionsend::Segments.remove(segment[:id])
312
325
  ```
@@ -24,16 +24,21 @@ module Millionsend
24
24
  Millionsend::Request.new(method: :patch, path: member_path(params), body: body).perform
25
25
  end
26
26
 
27
- # DELETE a contact by id or email.
28
- def remove(id_or_email)
29
- Millionsend::Request.new(method: :delete, path: member_path(id_or_email)).perform
27
+ # DELETE a contact by id or email. The contact's emails stay in the send
28
+ # log; erase: true also scrubs the address from email history, event
29
+ # payloads and API logs (a GDPR/LGPD erasure). The flag rides flat or in
30
+ # resend-ruby's addressing hash: remove(email: "a@x.dev", erase: true).
31
+ def remove(id_or_email, options = {})
32
+ erase = options.key?(:erase) ? options[:erase] : (id_or_email[:erase] if id_or_email.is_a?(Hash))
33
+ Millionsend::Request.new(method: :delete, path: member_path(id_or_email), query: { erase: erase }).perform
30
34
  end
31
35
 
32
- # GET /contacts — accepts limit:/after:/before:.
36
+ # GET /contacts — accepts limit:/after:/before: and include:
37
+ # (["properties", "topics"]) to carry the property map and the topic
38
+ # subscriptions on every item, as Contacts.get and Topics.list return them.
33
39
  def list(options = {})
34
- Millionsend::Request.new(
35
- method: :get, path: "/contacts", query: Millionsend::Util.list_query(options)
36
- ).perform
40
+ query = Millionsend::Util.list_query(options).merge(include: Millionsend::Util.include_query(options[:include]))
41
+ Millionsend::Request.new(method: :get, path: "/contacts", query: query).perform
37
42
  end
38
43
 
39
44
  # PATCH /contacts/:id_or_email/topics with a bare array of
@@ -53,8 +58,20 @@ module Millionsend
53
58
  # Every member method also accepts resend-ruby's addressing hash
54
59
  # ({ id: } / { email: } / { contact_id: }) in place of the bare value.
55
60
  def member_path(id_or_email)
56
- id_or_email = id_or_email[:email] || id_or_email[:id] || id_or_email[:contact_id] if id_or_email.is_a?(Hash)
57
- "/contacts/#{Millionsend::Util.encode(id_or_email)}"
61
+ "/contacts/#{Millionsend::Util.encode(unwrap(id_or_email))}"
62
+ end
63
+
64
+ # One /contacts/batch/get entry. The wire wants { id: } and { email: }
65
+ # told apart, and only an email carries "@".
66
+ def address(id_or_email)
67
+ value = unwrap(id_or_email)
68
+ value.to_s.include?("@") ? { email: value } : { id: value }
69
+ end
70
+
71
+ private
72
+
73
+ def unwrap(id_or_email)
74
+ id_or_email.is_a?(Hash) ? id_or_email[:email] || id_or_email[:id] || id_or_email[:contact_id] : id_or_email
58
75
  end
59
76
  end
60
77
 
@@ -96,8 +113,21 @@ module Millionsend
96
113
  ).perform
97
114
  end
98
115
 
116
+ # POST /contacts/batch/get — up to 1000 contacts by id or email (bare
117
+ # values or addressing hashes, like Contacts.get) in one request, one
118
+ # call against the rate limit. Returns { data: [...] } in request order
119
+ # plus missing: [{ index:, id: | email: }] for the entries that matched
120
+ # nobody — those never fail the call. include: ["properties", "topics"]
121
+ # attaches the same extras as Contacts.list.
122
+ def get(addresses, options = {})
123
+ body = { contacts: addresses.map { |a| Millionsend::Contacts.address(a) }, include: options[:include] }
124
+ Millionsend::Request.new(method: :post, path: "/contacts/batch/get", body: body.compact).perform
125
+ end
126
+
99
127
  # POST /contacts/batch/remove — { ids: [...] } or { emails: [...] }
100
- # (exactly one, up to 1000). Returns { data: [{ object:, contact:,
128
+ # (exactly one, up to 1000), plus erase: true to also scrub each
129
+ # address from email history, event payloads and API logs, as
130
+ # Contacts.remove does. Returns { data: [{ object:, contact:,
101
131
  # deleted: true }] } listing only the rows actually deleted; unknown
102
132
  # ids or addresses are skipped.
103
133
  def remove(params)
@@ -23,11 +23,12 @@ module Millionsend
23
23
  end
24
24
 
25
25
  # GET /segments/:id/contacts — the contacts currently matching, paginated
26
- # with limit:/after:/before:.
26
+ # with limit:/after:/before:; include: (["properties", "topics"]) as on
27
+ # Contacts.list.
27
28
  def contacts(id, options = {})
29
+ query = Millionsend::Util.list_query(options).merge(include: Millionsend::Util.include_query(options[:include]))
28
30
  Millionsend::Request.new(
29
- method: :get, path: "/segments/#{Millionsend::Util.encode(id)}/contacts",
30
- query: Millionsend::Util.list_query(options)
31
+ method: :get, path: "/segments/#{Millionsend::Util.encode(id)}/contacts", query: query
31
32
  ).perform
32
33
  end
33
34
 
@@ -33,6 +33,13 @@ module Millionsend
33
33
  { limit: options[:limit], after: options[:after], before: options[:before] }
34
34
  end
35
35
 
36
+ # ?include= for the contact lists: the names comma-joined, or nil (dropped
37
+ # from the query) when none were given.
38
+ def include_query(include)
39
+ names = Array(include)
40
+ names.empty? ? nil : names.join(",")
41
+ end
42
+
36
43
  # Per-request options for the Request constructor. Both call shapes land in
37
44
  # the same trailing positional hash: the original `idempotency_key: "k"` and
38
45
  # resend-ruby's keyword form `options: { idempotency_key: "k" }`, so the
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Millionsend
4
- VERSION = "0.6.0"
4
+ VERSION = "0.8.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: millionsend
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - MillionSend
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-04 00:00:00.000000000 Z
11
+ date: 2026-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake