anypost 1.0.0 → 1.1.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: 65b858025a9f3fda8fb8a21e6c704503e49eaad8f0fcac5913c4a7808524acb5
4
- data.tar.gz: 82615f81815ca2da06ae99b3e3a07c8e76eef059ec229c21a1324d5326f75030
3
+ metadata.gz: 626219a093ae533c0f15851744e4a6eb92a42e29f793c70ac6b219c64f74f5c2
4
+ data.tar.gz: 840398e4b95ff4e1bbdb6c1d5b719865304c9f0625f7e6c8f5de5b19cafbf45c
5
5
  SHA512:
6
- metadata.gz: dd83c2564df58e7dcc5c1819833578cd60040e1d25f0b0a2a8542c06968870b3b6615287dacddf95a7e058337a823e66f4394662a4fd26ba3ef20faf90219c7d
7
- data.tar.gz: ce4d157c91e82055a0bd191f6ce3cd6bb8505bd8be8dac779f67e3b592f96855b79c9caf82c8250a93eab85b278e862d36eea2b2d23aca98287d83837f4f2f58
6
+ metadata.gz: caab7720e980e41910338662984bc706b2d731b01d3ebf4938f3db65c534b2b6f3ddd67efb014439030d71e4092763a05557477f3a6dffd0a0b615652e2219d7
7
+ data.tar.gz: db2311ea2c576f98d120fb5e5f07c6914b3e4ee0c92a3fe64b94a7e294418a19b95529576070531f7ffb03a7f2393982a1581014ea0bcce34f6120c97da3b84f
data/README.md CHANGED
@@ -4,6 +4,8 @@ The official Ruby gem for the [Anypost](https://anypost.com) email API.
4
4
 
5
5
  Requires Ruby 3.2+. Built on [Faraday](https://github.com/lostisland/faraday).
6
6
 
7
+ This README covers the SDK itself: installation, idioms, and configuration. For platform concepts and the full field-level API reference, see the [Anypost documentation](https://anypost.com/docs).
8
+
7
9
  ## Install
8
10
 
9
11
  ```bash
@@ -24,10 +26,10 @@ require "anypost"
24
26
  client = Anypost::Client.new("ap_your_api_key")
25
27
 
26
28
  email = client.email.send(
27
- from: "Acme <you@yourdomain.com>",
28
- to: ["someone@example.com"],
29
- subject: "Hello from Anypost",
30
- html: "<p>It worked.</p>"
29
+ from: "YourCo <you@yourdomain.com>",
30
+ to: ["you@example.com"],
31
+ subject: "Welcome to Anypost",
32
+ html: "<p>Hello, inbox!</p>"
31
33
  )
32
34
 
33
35
  puts email.id
@@ -49,7 +51,7 @@ One of `text`, `html`, or `template_id` is required. All recipients in `to`, `cc
49
51
 
50
52
  ```ruby
51
53
  client.email.send(
52
- from: "Acme <you@yourdomain.com>",
54
+ from: "YourCo <you@yourdomain.com>",
53
55
  to: ["a@example.com", "b@example.com"],
54
56
  cc: ["team@example.com"],
55
57
  reply_to: "support@yourdomain.com",
@@ -60,11 +62,11 @@ client.email.send(
60
62
  )
61
63
  ```
62
64
 
63
- Attachment `content` is the raw file bytes pass what `File.binread` returns and the SDK base64-encodes it. Do not pre-encode it. The request body is capped at 5 MB.
65
+ Attachment `content` is the raw file bytes: pass what `File.binread` returns and the SDK base64-encodes it. Do not pre-encode it. The request body is capped at 5 MB.
64
66
 
65
67
  ```ruby
66
68
  client.email.send(
67
- from: "you@yourdomain.com",
69
+ from: "YourCo <you@yourdomain.com>",
68
70
  to: ["someone@example.com"],
69
71
  subject: "Your report",
70
72
  text: "Attached.",
@@ -78,20 +80,22 @@ Send with a published template and per-recipient variables:
78
80
 
79
81
  ```ruby
80
82
  client.email.send(
81
- from: "you@yourdomain.com",
83
+ from: "YourCo <you@yourdomain.com>",
82
84
  to: ["someone@example.com"],
83
85
  template_id: "template_018f2c5e-3a40-7a91-9c25-3a0b1d5e6f78",
84
86
  variables: {name: "Ada", plan: "pro"}
85
87
  )
86
88
  ```
87
89
 
90
+ See the [send reference](https://anypost.com/docs/reference/emails) for the complete field list.
91
+
88
92
  ## Batch
89
93
 
90
94
  Send 1 to 100 independent messages in one request. `defaults` fills any field an entry omits.
91
95
 
92
96
  ```ruby
93
97
  result = client.email.send_batch(
94
- defaults: {from: "you@yourdomain.com"},
98
+ defaults: {from: "YourCo <you@yourdomain.com>"},
95
99
  emails: [
96
100
  {to: ["a@example.com"], subject: "Hi A", text: "..."},
97
101
  {to: ["b@example.com"], subject: "Hi B", text: "..."}
@@ -115,7 +119,7 @@ end
115
119
 
116
120
  ## Domains
117
121
 
118
- Manage sending domains under `client.domains`. Add a domain, publish the CNAMEs it returns, then verify.
122
+ Manage sending domains under `client.domains`. Add a domain, publish the DNS records it returns, then verify.
119
123
 
120
124
  ```ruby
121
125
  domain = client.domains.create(name: "example.com")
@@ -123,21 +127,13 @@ domain = client.domains.create(name: "example.com")
123
127
  domain.dns_records.each do |record|
124
128
  puts "#{record.type} #{record.name} -> #{record.value}"
125
129
  end
126
- ```
127
-
128
- `verify` always returns the current domain — a still-`pending` domain does not raise. Read `status` and `verification_failure`, and poll while DNS propagates.
129
130
 
130
- ```ruby
131
131
  checked = client.domains.verify(domain.id)
132
+ # verify returns the current domain even while pending; it does not raise
132
133
  puts checked.verification_failure.code unless checked.status == "verified"
133
134
  ```
134
135
 
135
- `get`, `update` (tracking config only), and `delete` round out the resource:
136
-
137
- ```ruby
138
- client.domains.update(domain.id, tracking: {opens_enabled: true, clicks_enabled: true, subdomain: "track"})
139
- client.domains.delete(domain.id)
140
- ```
136
+ `get`, `update` (tracking config only), and `delete` round out the resource. See [Domains](https://anypost.com/docs/reference/domains) for the verification lifecycle and field reference.
141
137
 
142
138
  ## API keys
143
139
 
@@ -150,12 +146,9 @@ created = client.api_keys.create(
150
146
  allowed_domains: ["example.com"]
151
147
  )
152
148
  puts created.key # store now; never retrievable again
153
-
154
- client.api_keys.update(created.id, name: "Production server", permissions: "full")
155
- client.api_keys.delete(created.id)
156
149
  ```
157
150
 
158
- `get` returns metadata only `key_prefix`, never the secret. Permission and restriction changes take up to 5 minutes to propagate through the gateway cache.
151
+ `get` returns metadata only (`key_prefix`, never the secret); `update` and `delete` round out the resource. See [API keys](https://anypost.com/docs/reference/api-keys) for the permission model and cache propagation.
159
152
 
160
153
  ## Templates
161
154
 
@@ -168,30 +161,22 @@ template = client.templates.create(
168
161
  html: "<h1>Welcome, {{ name }}</h1>"
169
162
  )
170
163
 
171
- client.templates.update_draft(template.id, subject: "Welcome to Acme", html: "<h1>Welcome, {{ name }}</h1>")
172
164
  client.templates.publish(template.id)
173
165
  ```
174
166
 
175
- `kind` is `html` or `markdown` and is immutable once set. The plain-text body is always derived server-side. `get_draft`, `delete_draft`, `duplicate`, `get`, `update` (name only), and `delete` round out the resource. Send with a published template via `template_id` (see [Sending](#sending)).
167
+ `kind` (`html` or `markdown`) is immutable once set; the plain-text body is always derived server-side. `get_draft`, `update_draft`, `delete_draft`, `duplicate`, `get`, `update` (name only), and `delete` round out the resource. Send a published template with `template_id` (see [Sending](#sending)). See [Templates](https://anypost.com/docs/reference/templates) for the full model.
176
168
 
177
169
  ## Suppressions
178
170
 
179
- A suppression blocks sends to an address, scoped to a `topic`. The wildcard `*` blocks every topic; a specific topic (e.g. `marketing`) leaves transactional traffic untouched. Bounces and complaints write `*` automatically.
171
+ A suppression blocks sends to an address, scoped to a `topic`. The wildcard `*` blocks every topic; a named topic (e.g. `marketing`) leaves transactional traffic untouched.
180
172
 
181
173
  ```ruby
182
174
  client.suppressions.create(email: "alice@example.com", topic: "marketing", note: "Customer requested removal")
183
175
 
184
- row = client.suppressions.get("alice@example.com", "*")
185
176
  client.suppressions.delete("alice@example.com", "marketing")
186
177
  ```
187
178
 
188
- `list` accepts `email_contains`, `topic`, `reason`, and `origin` filters. `list_for_email` returns every row for an address across all topics; `delete_for_email` removes them all.
189
-
190
- ```ruby
191
- client.suppressions.list(reason: "complaint").each do |s|
192
- puts "#{s.email} #{s.topic} #{s.suppressed_at}"
193
- end
194
- ```
179
+ `get`, `list` (with `email_contains`, `topic`, `reason`, and `origin` filters), `list_for_email`, and `delete_for_email` round out the resource. See [Suppressions](https://anypost.com/docs/reference/suppressions) for scoping and the automatic-suppression rules for bounces and complaints.
195
180
 
196
181
  ## Webhooks
197
182
 
@@ -206,18 +191,11 @@ webhook = client.webhooks.create(
206
191
  puts webhook.signing_secret # store now; never retrievable again
207
192
  ```
208
193
 
209
- `update` sets the name, URL, events, and `status` together — set `status` to `"disabled"` to pause delivery, `"active"` to resume. `test` sends one synthetic `webhook.test` event and returns the outcome even when the endpoint fails. `rotate_secret` issues a new secret and keeps the previous one valid for a 24-hour grace window; `get`, `list`, and `delete` round out the resource.
210
-
211
- ```ruby
212
- result = client.webhooks.test(webhook.id)
213
- puts "#{result.status_code} #{result.error}" unless result.delivered
214
-
215
- rotated = client.webhooks.rotate_secret(webhook.id)
216
- ```
194
+ `update`, `test`, `rotate_secret`, `get`, `list`, and `delete` round out the resource. See [Webhooks](https://anypost.com/docs/reference/webhooks) for the event catalog, status transitions, and the secret-rotation grace window.
217
195
 
218
196
  ### Verifying deliveries
219
197
 
220
- `Anypost::WebhookSignature.verify` is a module method it needs the signing secret, not an API key, so call it in your handler without a client. Pass the **raw** request body (the exact bytes, before JSON parsing), the `Anypost-Signature` header, and the secret. It returns on success and raises `Anypost::WebhookVerificationError` otherwise. `Anypost::WebhookSignature.unwrap` does the same and returns the parsed delivery as a `Response`.
198
+ `Anypost::WebhookSignature.verify` is a module method. It needs the signing secret, not an API key, so call it in your handler without a client. Pass the **raw** request body (the exact bytes, before JSON parsing), the `Anypost-Signature` header, and the secret. It returns on success and raises `Anypost::WebhookVerificationError` otherwise. `Anypost::WebhookSignature.unwrap` does the same and returns the parsed delivery as a `Response`.
221
199
 
222
200
  ```ruby
223
201
  begin
@@ -231,7 +209,7 @@ rescue Anypost::WebhookVerificationError => e
231
209
  end
232
210
  ```
233
211
 
234
- Reach for `verify` when something else has already parsed the body. Keep the raw bytes for the verify step, then use your parsed object once it passes a Rack-style handler:
212
+ Reach for `verify` when something else has already parsed the body. Keep the raw bytes for the verify step, then use your parsed object once it passes (a Rack-style handler):
235
213
 
236
214
  ```ruby
237
215
  post "/anypost" do
@@ -247,11 +225,11 @@ post "/anypost" do
247
225
  end
248
226
  ```
249
227
 
250
- Deliveries older than five minutes are rejected by default to bound replay; pass `tolerance_seconds:` to widen, narrow, or disable (`0`) that check. During a secret rotation the header carries a `v1=` component per active secret, and a match on any one passes so deliveries keep verifying while you redeploy.
228
+ Deliveries older than five minutes are rejected by default to bound replay; pass `tolerance_seconds:` to widen, narrow, or disable (`0`) that check. During a secret rotation the header carries a `v1=` component per active secret, and a match on any one passes, so deliveries keep verifying while you redeploy.
251
229
 
252
230
  ## Events
253
231
 
254
- `client.events.list` pages the team's event stream, newest-first. The window defaults to the last 24 hours and is clamped to your plan's retention. Events are read-only and not addressable by id there is no `get`.
232
+ `client.events.list` pages the team's event stream, newest-first. The window defaults to the last 24 hours and is clamped to your plan's retention. Events are read-only and not addressable by id, so there is no `get`.
255
233
 
256
234
  ```ruby
257
235
  client.events.list(event_type: "email.bounced").each do |event|
@@ -259,16 +237,11 @@ client.events.list(event_type: "email.bounced").each do |event|
259
237
  end
260
238
  ```
261
239
 
262
- Filter by `start`, `end`, `event_type`, `recipient`, `email_id`, `message_id`, `domain`, `topic`, `campaign`, `template_id`, and `tags`. All filters are exact-match, except `tags`, which takes an array and matches an event carrying *any* of the given tags. A filter value that matches no row returns an empty page. This is also how you backfill the gap after a webhook endpoint was disabled page the events that occurred during the outage once it's healthy.
263
-
264
- ```ruby
265
- # Events tagged "onboarding" OR "welcome", that also bounced.
266
- page = client.events.list(tags: ["onboarding", "welcome"], event_type: "email.bounced")
267
- ```
240
+ Filter by `start`, `end`, `event_type`, `recipient`, `email_id`, `message_id`, `domain`, `topic`, `campaign`, `template_id`, `ip_pool`, and `tags`, an array that matches an event carrying *any* of the given tags. Every other filter is exact-match. This is also how you backfill the gap after a webhook endpoint was disabled: page the events that occurred during the outage once it's healthy. See [Events](https://anypost.com/docs/reference/events) for the field reference.
268
241
 
269
242
  ## Pagination
270
243
 
271
- List endpoints return a `Page`. Read one page directly, or iterate it to walk every page the client fetches each one as needed.
244
+ List endpoints return a `Page`. Read one page directly, or iterate it to walk every page; the client fetches each one as needed.
272
245
 
273
246
  ```ruby
274
247
  page = client.domains.list(limit: 50)
@@ -9,7 +9,7 @@ module Anypost
9
9
  # The window defaults to the last 24 hours and is clamped to the plan's
10
10
  # retention. Filter with `start`, `end`, `event_type`, `recipient`,
11
11
  # `email_id`, `message_id`, `domain`, `topic`, `campaign`, `template_id`,
12
- # and `tags` (an array, matched with hasAny).
12
+ # `ip_pool`, and `tags` (an array, matched with hasAny).
13
13
  def list(params = {})
14
14
  tags = params[:tags]
15
15
  paginate("/events", {
@@ -25,6 +25,10 @@ module Anypost
25
25
  topic: params[:topic],
26
26
  campaign: params[:campaign],
27
27
  template_id: params[:template_id],
28
+ # Exact match against the [a-z0-9]([a-z0-9-]*[a-z0-9])? pool-name
29
+ # shape. A value outside it returns an empty list rather than being
30
+ # ignored, so a typo cannot silently widen the answer to "all pools".
31
+ ip_pool: params[:ip_pool],
28
32
  # Sent comma-separated (tags=a,b); the API matches with hasAny.
29
33
  tags: (tags.is_a?(Array) && !tags.empty?) ? tags.join(",") : nil
30
34
  })
@@ -3,5 +3,5 @@
3
3
  module Anypost
4
4
  # The single source of truth for the gem version. Bump this, tag the commit
5
5
  # `vX.Y.Z`, and push — the release workflow builds and pushes to RubyGems.
6
- VERSION = "1.0.0"
6
+ VERSION = "1.1.0"
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anypost
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anypost
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-06-09 00:00:00.000000000 Z
11
+ date: 2026-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday