millionsend 0.5.0 → 0.6.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 +27 -5
- data/lib/millionsend/contacts.rb +22 -4
- data/lib/millionsend/version.rb +1 -1
- data/lib/millionsend/webhooks.rb +10 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0d6e46f436a0805ef763edac14bbd254e6f17503c598a8374b6abe9f45efcc76
|
|
4
|
+
data.tar.gz: 04c8ab67229c03c8769c7f9ef872b0e11b98d8aa8919a21c0d4c6ce6cc0582f9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ea4ff1283155e92f65aea2088a6ae4abdfd5032b3c93c243eb838a30ab141070c328bc7d2a278e0399894ec1e18300abef83a727ace9ed48a4b7e3271941e284
|
|
7
|
+
data.tar.gz: e845a9d24731c584b931a0de44fd7f0adee3760bcaa8d45d55412e301c6e9ad545cc1d62e294938e8adbdba07ebc204d156a96584d2d5b16fc721826853137dc
|
data/README.md
CHANGED
|
@@ -128,7 +128,12 @@ Millionsend::Contacts.list(limit: 50)
|
|
|
128
128
|
Millionsend::Contacts::Topics.update(email: "ada@acme.dev", topics: [{ id: topic_id, subscription: "opt_out" }]) # resend-ruby shape
|
|
129
129
|
Millionsend::Contacts.topics_update("ada@acme.dev", [{ id: topic_id, subscription: "opt_out" }]) # positional
|
|
130
130
|
Millionsend::Contacts::Topics.list(email: "ada@acme.dev") # GET /contacts/:id/topics — every topic with its effective
|
|
131
|
-
# subscription
|
|
131
|
+
# subscription (explicit: false when it is the topic's default)
|
|
132
|
+
# and its visibility ("public" | "private")
|
|
133
|
+
|
|
134
|
+
# Preference-center link (MillionSend extension) — POST /contacts/:id/preferences-link
|
|
135
|
+
link = Millionsend::Contacts.preferences_link("ada@acme.dev") # by id or email; also preferences_link(id: ...) / (email: ...)
|
|
136
|
+
link[:url] # the contact's hosted preference page; no expiry, so show it only to that contact
|
|
132
137
|
|
|
133
138
|
# Segment membership — POST / DELETE /contacts/:id/segments/:segment_id
|
|
134
139
|
Millionsend::Contacts::Segments.add("ada@acme.dev", segment_id)
|
|
@@ -143,6 +148,10 @@ result = Millionsend::Contacts::Batch.create(
|
|
|
143
148
|
result[:data] # [{ index:, id:, status: "created" | "updated" | "skipped" }]
|
|
144
149
|
result[:counts] # { created:, updated:, skipped:, failed: }
|
|
145
150
|
result[:errors] # permissive mode only: [{ index:, message: }]
|
|
151
|
+
|
|
152
|
+
# 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: [...]
|
|
154
|
+
# => { data: [{ object: "contact", contact: "<uuid>", deleted: true }, ...] } — only the rows actually deleted
|
|
146
155
|
```
|
|
147
156
|
|
|
148
157
|
Contacts are addressable by id or email; when an `update` hash carries both, the email wins.
|
|
@@ -237,14 +246,26 @@ hook = Millionsend::Webhooks.create(
|
|
|
237
246
|
signing_secret: "whsec_..." # optional: reuse an existing secret so the receiver keeps verifying
|
|
238
247
|
)
|
|
239
248
|
hook[:signing_secret]
|
|
240
|
-
Millionsend::Webhooks.get(hook[:id]) # also returns signing_secret
|
|
249
|
+
Millionsend::Webhooks.get(hook[:id]) # also returns signing_secret and previous_secret_expires_at
|
|
241
250
|
Millionsend::Webhooks.list
|
|
242
251
|
Millionsend::Webhooks.update(hook[:id], events: ["email.opened"], status: "disabled")
|
|
243
252
|
Millionsend::Webhooks.remove(hook[:id])
|
|
253
|
+
|
|
254
|
+
# Rotate the signing secret (MillionSend extension) — POST /webhooks/:id/rotate
|
|
255
|
+
rotated = Millionsend::Webhooks.rotate(hook[:id]) # mints a new secret, 24h overlap
|
|
256
|
+
rotated = Millionsend::Webhooks.rotate(hook[:id], signing_secret: "whsec_...", overlap_hours: 0) # bring your own, no overlap
|
|
257
|
+
rotated[:signing_secret] # the secret now signing deliveries
|
|
258
|
+
rotated[:previous_secret_expires_at] # ISO time until which the old secret also signs, or nil
|
|
244
259
|
```
|
|
245
260
|
|
|
261
|
+
During the overlap window (`overlap_hours`, 0–72, default 24) every delivery carries both
|
|
262
|
+
signatures, so a receiver holding either verifies; `Webhooks.get` reports the window's end
|
|
263
|
+
as `previous_secret_expires_at` (`nil` when none is open).
|
|
264
|
+
|
|
246
265
|
Events: `email.sent`, `email.delivered`, `email.delivery_delayed`, `email.bounced`,
|
|
247
|
-
`email.complained`, `email.opened`, `email.clicked`, `
|
|
266
|
+
`email.complained`, `email.opened`, `email.clicked`, `contact.created`, `contact.updated`,
|
|
267
|
+
`contact.deleted`, `contact.unsubscribed`, `contact.resubscribed`, `contact.topic_opt_in`,
|
|
268
|
+
`contact.topic_opt_out`, `suppression.added`, `suppression.removed`, `deliverability.warning`,
|
|
248
269
|
`deliverability.paused`, `quota.warning`, `quota.reached`, `quota.paused`.
|
|
249
270
|
|
|
250
271
|
### API keys
|
|
@@ -368,8 +389,9 @@ works as in resend-ruby. Notes:
|
|
|
368
389
|
Resend's audiences alias.
|
|
369
390
|
- Not in the API (yet), so not here: broadcast recipients/clicked links, email sharing and
|
|
370
391
|
metrics, contact imports, receiving, automations, logs, OAuth grants, webhook event replay.
|
|
371
|
-
- MillionSend extensions with no Resend counterpart: `Segments`, `Contacts::Batch`,
|
|
372
|
-
`
|
|
392
|
+
- MillionSend extensions with no Resend counterpart: `Segments`, `Contacts::Batch`,
|
|
393
|
+
`Contacts.preferences_link`, `Webhooks.rotate`, `Usage`, `Deliverability`,
|
|
394
|
+
`Emails.get_insights`, `Suppressions` `origin: "unsubscribe"`.
|
|
373
395
|
|
|
374
396
|
## License
|
|
375
397
|
|
data/lib/millionsend/contacts.rb
CHANGED
|
@@ -42,6 +42,14 @@ module Millionsend
|
|
|
42
42
|
Millionsend::Request.new(method: :patch, path: "#{member_path(id_or_email)}/topics", body: topics).perform
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
# POST /contacts/:id_or_email/preferences-link — the contact's hosted
|
|
46
|
+
# preference page, { object: "preferences_link", contact:, url: }. The
|
|
47
|
+
# url is a contact-scoped capability with no expiry: hand it only to that
|
|
48
|
+
# contact. 422 when the instance cannot build hosted links.
|
|
49
|
+
def preferences_link(id_or_email)
|
|
50
|
+
Millionsend::Request.new(method: :post, path: "#{member_path(id_or_email)}/preferences-link").perform
|
|
51
|
+
end
|
|
52
|
+
|
|
45
53
|
# Every member method also accepts resend-ruby's addressing hash
|
|
46
54
|
# ({ id: } / { email: } / { contact_id: }) in place of the bare value.
|
|
47
55
|
def member_path(id_or_email)
|
|
@@ -54,9 +62,10 @@ module Millionsend
|
|
|
54
62
|
module Topics
|
|
55
63
|
class << self
|
|
56
64
|
# GET /contacts/:id_or_email/topics — { id: | email: } or a bare id/email.
|
|
57
|
-
# Every topic comes back with the contact's effective subscription
|
|
58
|
-
# explicit: false means it is the topic's default, not a stored
|
|
59
|
-
# Unpaginated, like
|
|
65
|
+
# Every topic comes back with the contact's effective subscription
|
|
66
|
+
# (explicit: false means it is the topic's default, not a stored
|
|
67
|
+
# choice) and its visibility ("public" | "private"). Unpaginated, like
|
|
68
|
+
# Topics.list.
|
|
60
69
|
def list(params)
|
|
61
70
|
Millionsend::Request.new(method: :get, path: "#{Millionsend::Contacts.member_path(params)}/topics").perform
|
|
62
71
|
end
|
|
@@ -68,7 +77,8 @@ module Millionsend
|
|
|
68
77
|
end
|
|
69
78
|
end
|
|
70
79
|
|
|
71
|
-
# Bulk contact creation —
|
|
80
|
+
# Bulk contact creation and deletion — MillionSend extensions (Resend
|
|
81
|
+
# imports via CSV and deletes one at a time).
|
|
72
82
|
module Batch
|
|
73
83
|
class << self
|
|
74
84
|
# POST /contacts/batch with a bare array of up to 1000 create payloads.
|
|
@@ -85,6 +95,14 @@ module Millionsend
|
|
|
85
95
|
**Millionsend::Util.request_options(options)
|
|
86
96
|
).perform
|
|
87
97
|
end
|
|
98
|
+
|
|
99
|
+
# POST /contacts/batch/remove — { ids: [...] } or { emails: [...] }
|
|
100
|
+
# (exactly one, up to 1000). Returns { data: [{ object:, contact:,
|
|
101
|
+
# deleted: true }] } listing only the rows actually deleted; unknown
|
|
102
|
+
# ids or addresses are skipped.
|
|
103
|
+
def remove(params)
|
|
104
|
+
Millionsend::Request.new(method: :post, path: "/contacts/batch/remove", body: params).perform
|
|
105
|
+
end
|
|
88
106
|
end
|
|
89
107
|
end
|
|
90
108
|
|
data/lib/millionsend/version.rb
CHANGED
data/lib/millionsend/webhooks.rb
CHANGED
|
@@ -10,7 +10,8 @@ module Millionsend
|
|
|
10
10
|
Millionsend::Request.new(method: :post, path: "/webhooks", body: params).perform
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
# GET /webhooks/:id — includes signing_secret
|
|
13
|
+
# GET /webhooks/:id — includes signing_secret and
|
|
14
|
+
# previous_secret_expires_at (nil unless a rotation's overlap window is open).
|
|
14
15
|
def get(id)
|
|
15
16
|
Millionsend::Request.new(method: :get, path: member_path(id)).perform
|
|
16
17
|
end
|
|
@@ -30,6 +31,14 @@ module Millionsend
|
|
|
30
31
|
Millionsend::Request.new(method: :delete, path: member_path(id)).perform
|
|
31
32
|
end
|
|
32
33
|
|
|
34
|
+
# POST /webhooks/:id/rotate — signing_secret (optional whsec_ value to
|
|
35
|
+
# bring your own; omitted mints one) and overlap_hours (0..72, default
|
|
36
|
+
# 24) during which deliveries carry both signatures. Returns { object:,
|
|
37
|
+
# id:, signing_secret:, previous_secret_expires_at: }.
|
|
38
|
+
def rotate(id, params = {})
|
|
39
|
+
Millionsend::Request.new(method: :post, path: "#{member_path(id)}/rotate", body: params).perform
|
|
40
|
+
end
|
|
41
|
+
|
|
33
42
|
private
|
|
34
43
|
|
|
35
44
|
def member_path(id)
|