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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f44b4a952ce5037c565e1ba8071a29ddf7d2b7d875fd99cad7719e91155d3054
4
- data.tar.gz: a6b58d948cd026fe50ff598cc46129d6e9c164de71453dcdd77855237c0376fd
3
+ metadata.gz: d9260a596d597a0aad944da04656fb5c99606374b48b9e235f3e883917ef2bc6
4
+ data.tar.gz: 677e347b5604bd68edf198df96802d652470d3e4aef679460fdb24bce3563993
5
5
  SHA512:
6
- metadata.gz: 4c16c5f8ee728136b872cc3f6a83d78052c62649c7b69497973d72845fb318669073078eb54406863fb0d83cd5336868896e098da794677b50667eefb4f0ec7f
7
- data.tar.gz: d492c8d93ddcfff8e5f887f50aa7200fa0e30856effd041530a19a62cd2c2ad07ed47520c9f5fb57a32e4078103d6edad6619cb45cbba888cb94e0f01ff0faf8
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` to your instance.
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 = "ms_123"
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 http://localhost:3001
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 is self-hosted, so there is no cloud default **set `base_url` to your
55
- deployment in production.** An explicitly assigned value always wins over the environment.
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`, `NotFoundError`, `MissingApiKeyError`, `InvalidApiKeyError`,
330
- `RestrictedApiKeyError`, `SendingPausedError`, `BroadcastsPausedError`, `RateLimitExceededError`,
331
- `DailyQuotaExceededError`, `InvalidIdempotentRequestError`, `ConcurrentIdempotentRequestsError`,
332
- `InternalServerError`, and `ApplicationError` (the fallback for unknown names). Client-side and
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
@@ -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])
@@ -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,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Millionsend
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
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 http://localhost:3001 (MillionSend is
32
- # self-hosted, so there is no cloud default). Plain http is only accepted for
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 = "http://localhost:3001"
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.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 plus a base_url.'
58
+ so migrating is mostly an import swap.'
59
59
  email:
60
60
  executables: []
61
61
  extensions: []