Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,15 @@ A suppression blocks sends to an address, scoped to a `topic`. The wildcard `*`
client.suppressions.create(email: "alice@example.com", topic: "marketing", note: "Customer requested removal")

client.suppressions.delete("alice@example.com", "marketing")

client.suppressions.create_batch([
{email: "bob@example.com"},
{email: "carol@example.com", topic: "marketing", reason: "unsubscribed"}
])
```

`create_batch` takes up to 1,000 items per call and returns `imported`, `already_suppressed`, and `invalid`. Existing entries are left unchanged, so retrying a batch is safe, and invalid addresses are reported in `invalid` instead of failing the call. A batch that would pass your team's suppression limit raises a validation error and adds nothing. Suppressions do not expire.

`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.

## Webhooks
Expand Down
16 changes: 15 additions & 1 deletion lib/anypost/resources/suppressions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Anypost
module Resources
# Operations on the `/suppressions` endpoints. Entries key on `(email, topic)`.
class Suppressions < Base
# List the team's suppressions, newest-first. Expired rows are filtered out.
# List the team's suppressions, newest-first.
# Filter with `email_contains`, `topic`, `reason`, and `origin`.
def list(params = {})
paginate("/suppressions", {
Expand All @@ -23,6 +23,20 @@ def create(params)
request_object(:post, "/suppressions", body: params)
end

# Add up to 1,000 suppressions in one call. Pass the list of items, each a
# hash with `email` and optionally `topic` (default `*`), `reason` (a free-form
# string; common aliases like `bounce` or `opt-out` are mapped, anything else
# records `manual`), and `note`. Entries that already exist are counted in
# `already_suppressed` and left unchanged, so a batch is safe to retry.
# Invalid addresses come back in `invalid` (with their `index` and `email`)
# instead of failing the batch. Raises validation_error, adding nothing, if
# the batch would pass the team's suppression limit.
#
# @return [Response] with `imported`, `already_suppressed`, and `invalid`
def create_batch(suppressions)
request_object(:post, "/suppressions/batch", body: {suppressions: suppressions})
end

# Retrieve the suppression for an `(email, topic)` pair. Use `*` as the
# topic for the global row. Raises not_found if the pair isn't suppressed.
def get(email, topic)
Expand Down
2 changes: 1 addition & 1 deletion lib/anypost/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
module Anypost
# The single source of truth for the gem version. Bump this, tag the commit
# `vX.Y.Z`, and push — the release workflow builds and pushes to RubyGems.
VERSION = "1.2.0"
VERSION = "1.3.0"
end
21 changes: 21 additions & 0 deletions spec/resources_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,27 @@
expect(server.last_request.method).to eq(:delete)
end

it "adds suppressions in a batch and parses invalid entries" do
server.stub(:post, "/v1/suppressions/batch", body: {
imported: 1, already_suppressed: 0, invalid: [{index: 1, email: "not-an-address"}]
})

result = client.suppressions.create_batch([
{email: "a@b.com", reason: "hard bounce"},
{email: "not-an-address"}
])

expect(server.last_request.method).to eq(:post)
expect(server.last_request.path).to eq("/v1/suppressions/batch")
expect(server.last_request.json_body).to eq(
"suppressions" => [{"email" => "a@b.com", "reason" => "hard bounce"}, {"email" => "not-an-address"}]
)
expect(result.imported).to eq(1)
expect(result.already_suppressed).to eq(0)
expect(result.invalid.first.index).to eq(1)
expect(result.invalid.first.email).to eq("not-an-address")
end

it "lists every suppression for an email" do
server.stub(:get, "/v1/suppressions/a%40b.com", body: {
data: [{email: "a@b.com", topic: "*"}, {email: "a@b.com", topic: "news"}]
Expand Down
Loading