millionsend 0.4.0 → 0.5.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 +19 -12
- data/lib/millionsend/contacts.rb +8 -0
- data/lib/millionsend/error.rb +2 -0
- data/lib/millionsend/version.rb +1 -1
- data/lib/millionsend.rb +3 -3
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d9260a596d597a0aad944da04656fb5c99606374b48b9e235f3e883917ef2bc6
|
|
4
|
+
data.tar.gz: 677e347b5604bd68edf198df96802d652470d3e4aef679460fdb24bce3563993
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb4c0031b1c2b22911ebba5b0f5d45c05b613196b2e665c11212d5146c5e7102c4cdbba343245acf814bffc6e8394d603c2c124d325a25c4c70fc1a49ac21789
|
|
7
|
+
data.tar.gz: bae43393a0387f459f882638a9eb6a7961cdc6d9b94b602d914cd199c071ce7e403706610e89e1426273e347cb1a3d6393518cf7d9f91d567352692c8765f2fe
|
data/README.md
CHANGED
|
@@ -5,7 +5,7 @@ self-hostable, Resend-compatible email API on AWS SES.
|
|
|
5
5
|
|
|
6
6
|
The API is wire-compatible with Resend, and this gem deliberately mirrors the shape of
|
|
7
7
|
[`resend`](https://github.com/resend/resend-ruby), so migrating is mostly a find-and-replace:
|
|
8
|
-
swap the constant, set `base_url`
|
|
8
|
+
swap the constant (and, on a self-hosted instance, set `base_url`).
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
@@ -26,8 +26,8 @@ Requires Ruby 3.0+. Only the standard library is used at runtime (`net/http`, `j
|
|
|
26
26
|
```ruby
|
|
27
27
|
require "millionsend"
|
|
28
28
|
|
|
29
|
-
Millionsend.api_key
|
|
30
|
-
Millionsend.base_url = "https://mail.acme.dev"
|
|
29
|
+
Millionsend.api_key = "ms_123"
|
|
30
|
+
# Millionsend.base_url = "https://mail.acme.dev" # self-hosted only; defaults to MillionSend Cloud
|
|
31
31
|
|
|
32
32
|
email = Millionsend::Emails.send(
|
|
33
33
|
from: "Acme <onboarding@acme.dev>",
|
|
@@ -47,12 +47,12 @@ on any non-2xx response (see [Error handling](#error-handling)).
|
|
|
47
47
|
```ruby
|
|
48
48
|
Millionsend.api_key = "ms_123" # falls back to ENV["MILLIONSEND_API_KEY"]
|
|
49
49
|
Millionsend.base_url = "https://mail.acme.dev" # falls back to ENV["MILLIONSEND_BASE_URL"],
|
|
50
|
-
# then
|
|
50
|
+
# then https://api.millionsend.com (Cloud)
|
|
51
51
|
Millionsend.allow_insecure_http = false # accept a non-loopback http:// base_url
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
MillionSend
|
|
55
|
-
|
|
54
|
+
MillionSend Cloud works with just the API key; a self-hosted instance sets `base_url` to
|
|
55
|
+
its origin. An explicitly assigned value always wins over the environment.
|
|
56
56
|
Plain `http://` is only accepted for loopback hosts (`localhost`, `127.0.0.1`, `::1`); any
|
|
57
57
|
other `http://` URL raises `Millionsend::ApplicationError` on the first call, since the API
|
|
58
58
|
key is sent as a bearer header. Set `allow_insecure_http = true` to talk to a non-TLS
|
|
@@ -127,6 +127,8 @@ Millionsend::Contacts.list(limit: 50)
|
|
|
127
127
|
# Topic subscriptions (granular unsubscribe) — PATCH /contacts/:id/topics
|
|
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
|
+
Millionsend::Contacts::Topics.list(email: "ada@acme.dev") # GET /contacts/:id/topics — every topic with its effective
|
|
131
|
+
# subscription; explicit: false when it is the topic's default
|
|
130
132
|
|
|
131
133
|
# Segment membership — POST / DELETE /contacts/:id/segments/:segment_id
|
|
132
134
|
Millionsend::Contacts::Segments.add("ada@acme.dev", segment_id)
|
|
@@ -243,7 +245,7 @@ Millionsend::Webhooks.remove(hook[:id])
|
|
|
243
245
|
|
|
244
246
|
Events: `email.sent`, `email.delivered`, `email.delivery_delayed`, `email.bounced`,
|
|
245
247
|
`email.complained`, `email.opened`, `email.clicked`, `deliverability.warning`,
|
|
246
|
-
`deliverability.paused`, `quota.warning`, `quota.reached`.
|
|
248
|
+
`deliverability.paused`, `quota.warning`, `quota.reached`, `quota.paused`.
|
|
247
249
|
|
|
248
250
|
### API keys
|
|
249
251
|
|
|
@@ -326,12 +328,16 @@ rescue Millionsend::Error => e
|
|
|
326
328
|
end
|
|
327
329
|
```
|
|
328
330
|
|
|
329
|
-
Subclasses: `ValidationError`, `
|
|
330
|
-
`
|
|
331
|
-
`
|
|
332
|
-
`
|
|
331
|
+
Subclasses: `ValidationError`, `AllRecipientsSuppressedError`, `NotFoundError`,
|
|
332
|
+
`MissingApiKeyError`, `InvalidApiKeyError`, `RestrictedApiKeyError`, `SendingPausedError`,
|
|
333
|
+
`BroadcastsPausedError`, `RateLimitExceededError`, `DailyQuotaExceededError`,
|
|
334
|
+
`InvalidIdempotentRequestError`, `ConcurrentIdempotentRequestsError`, `InternalServerError`, and
|
|
335
|
+
`ApplicationError` (the fallback for unknown names). Client-side and
|
|
333
336
|
transport failures that never reached the API raise `ApplicationError` with `#status_code == nil`.
|
|
334
337
|
|
|
338
|
+
`Emails.send` and `Batch.send` raise `AllRecipientsSuppressedError` (`all_recipients_suppressed`,
|
|
339
|
+
422) when every `to` recipient is on the suppression list or opted out of the send's `topic_id`.
|
|
340
|
+
|
|
335
341
|
## Migrating from Resend
|
|
336
342
|
|
|
337
343
|
```diff
|
|
@@ -340,7 +346,7 @@ transport failures that never reached the API raise `ApplicationError` with `#st
|
|
|
340
346
|
- Resend::Emails.send(from: "...", to: "...", subject: "Hi", html: "<p>hi</p>")
|
|
341
347
|
+ require "millionsend"
|
|
342
348
|
+ Millionsend.api_key = "ms_123"
|
|
343
|
-
+ Millionsend.base_url = "https://mail.acme.dev"
|
|
349
|
+
+ Millionsend.base_url = "https://mail.acme.dev" # self-hosted only
|
|
344
350
|
+ Millionsend::Emails.send(from: "...", to: "...", subject: "Hi", html: "<p>hi</p>")
|
|
345
351
|
```
|
|
346
352
|
|
|
@@ -354,6 +360,7 @@ works as in resend-ruby. Notes:
|
|
|
354
360
|
accepts both.
|
|
355
361
|
- `Contacts` member methods and `Contacts::Segments` / `Contacts::Topics` accept resend-ruby's
|
|
356
362
|
addressing hashes (`id:` / `email:` / `contact_id:`, `segment_id:`) as well as bare values.
|
|
363
|
+
`Contacts::Topics.list` is unpaginated, so resend-ruby's `limit:`/`after:`/`before:` are not sent.
|
|
357
364
|
`Suppressions::Batch` is nested as in resend-ruby.
|
|
358
365
|
- **No audiences** — contacts are team-global, so there is no `Audiences` resource and no
|
|
359
366
|
`audience_id` params. The API's `/audiences/*` routes are a compatibility shim and are not
|
data/lib/millionsend/contacts.rb
CHANGED
|
@@ -53,6 +53,14 @@ module Millionsend
|
|
|
53
53
|
# Topic subscriptions of one contact, in resend-ruby's nested shape.
|
|
54
54
|
module Topics
|
|
55
55
|
class << self
|
|
56
|
+
# 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 choice.
|
|
59
|
+
# Unpaginated, like Topics.list.
|
|
60
|
+
def list(params)
|
|
61
|
+
Millionsend::Request.new(method: :get, path: "#{Millionsend::Contacts.member_path(params)}/topics").perform
|
|
62
|
+
end
|
|
63
|
+
|
|
56
64
|
# PATCH /contacts/:id_or_email/topics — { id: | email:, topics: [{ id:, subscription: }] }.
|
|
57
65
|
def update(params)
|
|
58
66
|
Millionsend::Contacts.topics_update(params, params[:topics])
|
data/lib/millionsend/error.rb
CHANGED
|
@@ -31,6 +31,7 @@ module Millionsend
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
class ValidationError < Error; end
|
|
34
|
+
class AllRecipientsSuppressedError < Error; end
|
|
34
35
|
class NotFoundError < Error; end
|
|
35
36
|
class MissingApiKeyError < Error; end
|
|
36
37
|
class InvalidApiKeyError < Error; end
|
|
@@ -46,6 +47,7 @@ module Millionsend
|
|
|
46
47
|
|
|
47
48
|
ERROR_TYPES = {
|
|
48
49
|
"validation_error" => ValidationError,
|
|
50
|
+
"all_recipients_suppressed" => AllRecipientsSuppressedError,
|
|
49
51
|
"not_found" => NotFoundError,
|
|
50
52
|
"missing_api_key" => MissingApiKeyError,
|
|
51
53
|
"invalid_api_key" => InvalidApiKeyError,
|
data/lib/millionsend/version.rb
CHANGED
data/lib/millionsend.rb
CHANGED
|
@@ -28,13 +28,13 @@ require "millionsend/deliverability"
|
|
|
28
28
|
# subject: "Hi", html: "<strong>it works</strong>")
|
|
29
29
|
#
|
|
30
30
|
# api_key falls back to the MILLIONSEND_API_KEY env var; base_url to
|
|
31
|
-
# MILLIONSEND_BASE_URL and then
|
|
32
|
-
#
|
|
31
|
+
# MILLIONSEND_BASE_URL and then MillionSend Cloud, so a self-hosted instance
|
|
32
|
+
# sets its own origin. Plain http is only accepted for
|
|
33
33
|
# loopback hosts unless allow_insecure_http is set, since the API key travels
|
|
34
34
|
# as a bearer header. Every call returns a symbol-keyed Hash on success and
|
|
35
35
|
# raises a Millionsend::Error on any non-2xx response.
|
|
36
36
|
module Millionsend
|
|
37
|
-
DEFAULT_BASE_URL = "
|
|
37
|
+
DEFAULT_BASE_URL = "https://api.millionsend.com"
|
|
38
38
|
USER_AGENT = "millionsend-ruby/#{VERSION}"
|
|
39
39
|
|
|
40
40
|
class << self
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: millionsend
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- MillionSend
|
|
@@ -55,7 +55,7 @@ dependencies:
|
|
|
55
55
|
description: 'Ruby client for the MillionSend HTTP API: emails, batch, contacts, contact
|
|
56
56
|
properties, topics, broadcasts, segments, suppressions, domains, webhooks, API keys,
|
|
57
57
|
templates and usage. Wire-compatible with Resend and mirror-shaped after resend-ruby,
|
|
58
|
-
so migrating is mostly an import swap
|
|
58
|
+
so migrating is mostly an import swap.'
|
|
59
59
|
email:
|
|
60
60
|
executables: []
|
|
61
61
|
extensions: []
|