sendping 1.0.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +469 -0
- data/lib/sendping/api_keys.rb +19 -0
- data/lib/sendping/audiences.rb +39 -0
- data/lib/sendping/automations.rb +100 -0
- data/lib/sendping/campaigns.rb +81 -0
- data/lib/sendping/client.rb +265 -0
- data/lib/sendping/contact_properties.rb +33 -0
- data/lib/sendping/contacts.rb +188 -0
- data/lib/sendping/domains.rb +103 -0
- data/lib/sendping/emails.rb +165 -0
- data/lib/sendping/error.rb +109 -0
- data/lib/sendping/events.rb +53 -0
- data/lib/sendping/logs.rb +24 -0
- data/lib/sendping/polls.rb +18 -0
- data/lib/sendping/segments.rb +52 -0
- data/lib/sendping/templates.rb +42 -0
- data/lib/sendping/topics.rb +38 -0
- data/lib/sendping/version.rb +5 -0
- data/lib/sendping/webhooks.rb +265 -0
- data/lib/sendping.rb +81 -0
- metadata +83 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SendPing
|
|
4
|
+
# Send and inspect emails. Inbound mail lives under SendPing::Emails::Receiving.
|
|
5
|
+
module Emails
|
|
6
|
+
class << self
|
|
7
|
+
# Send a single email. POST /emails
|
|
8
|
+
# SendPing::Emails.send({ from: "...", to: ["..."], subject: "...", html: "..." })
|
|
9
|
+
#
|
|
10
|
+
# Write ordinary links. Anchors, markdown links and bare URLs/domains in
|
|
11
|
+
# BOTH the :html and :text bodies are converted to tracked redirects at
|
|
12
|
+
# send time (when the sending domain has click tracking on), so a click is
|
|
13
|
+
# recorded whichever alternative the recipient's mail client renders.
|
|
14
|
+
# Opt-out links are never wrapped, and the content you send is stored and
|
|
15
|
+
# returned UNCHANGED — only the delivered copy carries the tracking URL.
|
|
16
|
+
# Pass { idempotency_key: "order-123" } as the second argument to retry safely.
|
|
17
|
+
def send(params, options = {})
|
|
18
|
+
Client.request(:post, "/emails", body: params, options: options)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Send up to 100 emails in one request. POST /emails/batch (alias of Batch.send).
|
|
22
|
+
# Batch items reject `attachments` and `scheduled_at` — send those
|
|
23
|
+
# individually via Emails.send.
|
|
24
|
+
def batch(payloads, options = {})
|
|
25
|
+
Client.request(:post, "/emails/batch", body: payloads, options: options)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# List sent emails (trimmed list items) — cursor pagination plus optional
|
|
29
|
+
# server-side filters: `campaign_id`, `automation_id`, `source`
|
|
30
|
+
# ("individual" for all one-off sends with no campaign/automation origin,
|
|
31
|
+
# "api" for one-off sends made with an API key — which also covers mail
|
|
32
|
+
# sent before the send origin was recorded — or "dashboard" for one-off
|
|
33
|
+
# mail composed in the dashboard: the composer and inbox replies/forwards;
|
|
34
|
+
# honoured only when neither `campaign_id` nor `automation_id` is
|
|
35
|
+
# supplied), `domain_id`, `status` (matched case-insensitively
|
|
36
|
+
# against the row's `last_event`), `folder` (one of "outbox", "sent",
|
|
37
|
+
# "scheduled" or "failed" — any other value is rejected with a 422) and
|
|
38
|
+
# `search` (recipients, subject and sender). `q` is the server's alias
|
|
39
|
+
# for `search`, honoured only when `search` is absent. GET /emails
|
|
40
|
+
def list(params = {})
|
|
41
|
+
query = Client.pagination(params).merge(
|
|
42
|
+
Client.filters(params, :campaign_id, :automation_id, :source, :domain_id, :status, :search, :q, :folder)
|
|
43
|
+
)
|
|
44
|
+
Client.request(:get, "/emails", query: query)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Per-source send metrics, one row per origin. `kind` is "campaign",
|
|
48
|
+
# "automation", "api" (one-off API-key sends, including mail sent before
|
|
49
|
+
# the send origin was recorded) or "individual" (dashboard-composed
|
|
50
|
+
# one-offs); `id`, `name`, `subject` and `status` are null for both the
|
|
51
|
+
# "api" and "individual" rows. Not paginated. GET /emails/sources
|
|
52
|
+
def sources
|
|
53
|
+
Client.request(:get, "/emails/sources")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Retrieve a sent email and its events. GET /emails/:id
|
|
57
|
+
def get(email_id)
|
|
58
|
+
Client.request(:get, "/emails/#{Client.path_escape(email_id)}")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# List a sent email's attachments. GET /emails/:id/attachments
|
|
62
|
+
def list_attachments(email_id)
|
|
63
|
+
Client.request(:get, "/emails/#{Client.path_escape(email_id)}/attachments")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Retrieve one attachment's metadata. GET /emails/:id/attachments/:attachment_id
|
|
67
|
+
def get_attachment(email_id, attachment_id)
|
|
68
|
+
Client.request(:get, "/emails/#{Client.path_escape(email_id)}/attachments/#{Client.path_escape(attachment_id)}")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Reschedule a scheduled email. PATCH /emails/:id
|
|
72
|
+
# SendPing::Emails.update("email_id", { scheduled_at: "2026-08-01T09:00:00Z" })
|
|
73
|
+
def update(email_id, params)
|
|
74
|
+
Client.request(:patch, "/emails/#{Client.path_escape(email_id)}", body: params)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Cancel a scheduled email. POST /emails/:id/cancel
|
|
78
|
+
def cancel(email_id)
|
|
79
|
+
Client.request(:post, "/emails/#{Client.path_escape(email_id)}/cancel")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Inbound (received) email.
|
|
84
|
+
module Receiving
|
|
85
|
+
class << self
|
|
86
|
+
# List received emails — cursor pagination plus an optional
|
|
87
|
+
# `received_for` filter (only messages received for that address).
|
|
88
|
+
# With no `limit` and no cursor the endpoint returns up to 1000 rows in
|
|
89
|
+
# one response; pass `limit` to get normal 1-100 pages.
|
|
90
|
+
# GET /emails/receiving
|
|
91
|
+
def list(params = {})
|
|
92
|
+
query = Client.pagination(params).merge(Client.filters(params, :received_for))
|
|
93
|
+
Client.request(:get, "/emails/receiving", query: query)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Per-address inbound stats (totals, replies, last received).
|
|
97
|
+
# Not paginated. GET /emails/receiving/addresses
|
|
98
|
+
def list_addresses
|
|
99
|
+
Client.request(:get, "/emails/receiving/addresses")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Retrieve a received email. GET /emails/receiving/:id
|
|
103
|
+
def get(email_id)
|
|
104
|
+
Client.request(:get, "/emails/receiving/#{Client.path_escape(email_id)}")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# List a received email's attachments. With no `limit` and no `after`
|
|
108
|
+
# one page carries up to 1,000 of them and `has_more` reports any
|
|
109
|
+
# truncation; supplying either paginates normally.
|
|
110
|
+
# GET /emails/receiving/:id/attachments
|
|
111
|
+
def list_attachments(email_id, params = {})
|
|
112
|
+
Client.request(
|
|
113
|
+
:get,
|
|
114
|
+
"/emails/receiving/#{Client.path_escape(email_id)}/attachments",
|
|
115
|
+
query: Client.pagination(params)
|
|
116
|
+
)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Download one attachment as raw bytes (binary String).
|
|
120
|
+
# GET /emails/receiving/:id/attachments/:attachment_id
|
|
121
|
+
def get_attachment(email_id, attachment_id)
|
|
122
|
+
Client.request(
|
|
123
|
+
:get,
|
|
124
|
+
"/emails/receiving/#{Client.path_escape(email_id)}/attachments/#{Client.path_escape(attachment_id)}",
|
|
125
|
+
raw: true
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Download the original RFC822/MIME message as raw bytes (binary String).
|
|
130
|
+
# GET /emails/receiving/:id/raw
|
|
131
|
+
def get_raw(email_id)
|
|
132
|
+
Client.request(:get, "/emails/receiving/#{Client.path_escape(email_id)}/raw", raw: true)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Forward a received email. POST /emails/receiving/:id/forward
|
|
136
|
+
# Receiving.forward(id, { from: "you@yourdomain.com", to: "team@you.com" })
|
|
137
|
+
def forward(email_id, params, options = {})
|
|
138
|
+
Client.request(:post, "/emails/receiving/#{Client.path_escape(email_id)}/forward", body: params, options: options)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Reply to a received email's sender, threaded into the conversation.
|
|
142
|
+
# POST /emails/receiving/:id/reply
|
|
143
|
+
def reply(email_id, params, options = {})
|
|
144
|
+
Client.request(:post, "/emails/receiving/#{Client.path_escape(email_id)}/reply", body: params, options: options)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Delete a received email. DELETE /emails/receiving/:id
|
|
148
|
+
def delete(email_id)
|
|
149
|
+
Client.request(:delete, "/emails/receiving/#{Client.path_escape(email_id)}")
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Batch send — SendPing::Batch.send([...]). POST /emails/batch
|
|
156
|
+
# Batch items reject `attachments` and `scheduled_at` — send those
|
|
157
|
+
# individually via SendPing::Emails.send.
|
|
158
|
+
module Batch
|
|
159
|
+
class << self
|
|
160
|
+
def send(payloads, options = {})
|
|
161
|
+
Client.request(:post, "/emails/batch", body: payloads, options: options)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SendPing
|
|
4
|
+
# Raised for every non-2xx API response. Mirrors the API error shape
|
|
5
|
+
# { statusCode, name, message }:
|
|
6
|
+
#
|
|
7
|
+
# begin
|
|
8
|
+
# SendPing::Emails.send(params)
|
|
9
|
+
# rescue SendPing::Error => e
|
|
10
|
+
# e.status_code # => 422
|
|
11
|
+
# e.name # => "validation_error"
|
|
12
|
+
# e.message # => "The `from` address must use a verified domain."
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# Match on #name, never on #message — messages are scrubbed of provider
|
|
16
|
+
# identifiers server-side and are not a stable contract. A handler may also
|
|
17
|
+
# answer with a status other than the one a name usually maps to, so read
|
|
18
|
+
# #status_code rather than assuming one from the name.
|
|
19
|
+
#
|
|
20
|
+
# Some errors carry additive fields on top of those three. The whole parsed
|
|
21
|
+
# body is kept on #body, with the common extras surfaced as readers that
|
|
22
|
+
# return nil on an ordinary error:
|
|
23
|
+
#
|
|
24
|
+
# rescue SendPing::Error => e
|
|
25
|
+
# if (cap = e.limit) # WHICH quota ran out
|
|
26
|
+
# cap["kind"] # => "emails_daily"
|
|
27
|
+
# cap["used"]; cap["limit"] # => 100, 100
|
|
28
|
+
# cap.dig("next_plan", "name") # => "Pro"
|
|
29
|
+
# end
|
|
30
|
+
# e.reputation # reputation gates
|
|
31
|
+
# e.sent # a batch that failed part way through
|
|
32
|
+
# e.sent_count # — do NOT resend these
|
|
33
|
+
# end
|
|
34
|
+
class Error < StandardError
|
|
35
|
+
attr_reader :status_code, :error_name
|
|
36
|
+
|
|
37
|
+
# The full parsed error body ({} when the response was not a JSON object).
|
|
38
|
+
# Read it for any additive field newer than this SDK version.
|
|
39
|
+
attr_reader :body
|
|
40
|
+
|
|
41
|
+
def initialize(message = nil, status_code: nil, error_name: nil, body: nil)
|
|
42
|
+
super(message)
|
|
43
|
+
@status_code = status_code
|
|
44
|
+
@error_name = error_name
|
|
45
|
+
@body = body.is_a?(Hash) ? body : {}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The API error `name` (e.g. "validation_error", "not_found").
|
|
49
|
+
def name
|
|
50
|
+
@error_name
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Original email for a failed or unconfirmed send; inspect before resending.
|
|
54
|
+
def id
|
|
55
|
+
value = @body["id"]
|
|
56
|
+
value.is_a?(String) ? value : nil
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Reserved prefix, including uncertain sends. Never automatically resend it.
|
|
60
|
+
def reserved
|
|
61
|
+
value = @body["reserved"]
|
|
62
|
+
value.is_a?(Array) ? value : nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# The never-attempted tail; distinct from uncertain reserved items.
|
|
66
|
+
def unsent_count
|
|
67
|
+
value = @body["unsent_count"]
|
|
68
|
+
value.is_a?(Integer) ? value : nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# The plan/quota cap this request hit, else nil. Carried by
|
|
72
|
+
# plan_limit_reached, every *_quota_exceeded, contact_limit_reached and
|
|
73
|
+
# ai_credits_exceeded — it says WHICH quota ran out, how much of it was
|
|
74
|
+
# used, and the cheapest plan that would fit.
|
|
75
|
+
def limit
|
|
76
|
+
hash_field("limit")
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# The reputation-gate detail on reputation_paused /
|
|
80
|
+
# reputation_limit_exceeded, else nil. Carries at least "retryable" and
|
|
81
|
+
# "scope" ("tenant" | "domain" | "platform").
|
|
82
|
+
def reputation
|
|
83
|
+
hash_field("reputation")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# The emails that were already sent before a batch failed part way through
|
|
87
|
+
# (POST /emails/batch with an idempotency_key), else nil. Do NOT resend them.
|
|
88
|
+
def sent
|
|
89
|
+
value = @body["sent"]
|
|
90
|
+
value.is_a?(Array) ? value : nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# How many emails went out before a batch failed part way through, else nil.
|
|
94
|
+
# Falls back to #sent's size when the body carried the list but not the count.
|
|
95
|
+
def sent_count
|
|
96
|
+
count = @body["sent_count"]
|
|
97
|
+
return count if count.is_a?(Integer)
|
|
98
|
+
|
|
99
|
+
sent&.size
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def hash_field(key)
|
|
105
|
+
value = @body[key]
|
|
106
|
+
value.is_a?(Hash) ? value : nil
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SendPing
|
|
4
|
+
# Custom events that automations trigger on. Events are DOMAIN-FIRST:
|
|
5
|
+
# `domain` is required on send, and only that domain's automations fire.
|
|
6
|
+
module Events
|
|
7
|
+
class << self
|
|
8
|
+
# Send a custom event. POST /events/send
|
|
9
|
+
# SendPing::Events.send({ event: "signup.completed", domain: "yourdomain.com",
|
|
10
|
+
# email: "user@example.com", payload: { plan: "pro" } })
|
|
11
|
+
# Identify the contact by `contact_id` OR `email`. Event names cannot
|
|
12
|
+
# start with the reserved "sendping:" prefix.
|
|
13
|
+
#
|
|
14
|
+
# NOTE: only POST /emails, POST /emails/batch, and received-email reply/forward honour `Idempotency-Key`.
|
|
15
|
+
# An `idempotency_key` passed here is still forwarded, but the server
|
|
16
|
+
# ignores it, so a retry ingests a SECOND event and can enroll the contact
|
|
17
|
+
# twice — de-duplicate on your side instead.
|
|
18
|
+
def send(params, options = {})
|
|
19
|
+
Client.require_domain!(params, "Events.send")
|
|
20
|
+
Client.request(:post, "/events/send", body: params, options: options)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Create a custom-event definition (name + optional payload schema).
|
|
24
|
+
# Schema values are one of "string", "number", "boolean", "date".
|
|
25
|
+
# POST /events
|
|
26
|
+
# SendPing::Events.create({ name: "signup.completed", schema: { plan: "string" } })
|
|
27
|
+
#
|
|
28
|
+
# NOTE: `options[:idempotency_key]` carries no guarantee here — see
|
|
29
|
+
# `send`. A duplicate event name is already a 422 validation_error.
|
|
30
|
+
def create(params, options = {})
|
|
31
|
+
Client.request(:post, "/events", body: params, options: options)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Update a definition's payload schema. PATCH /events/:id
|
|
35
|
+
# The event NAME is immutable (automations reference it) — passing `name`
|
|
36
|
+
# is a 422; pass `schema: nil` to clear the schema.
|
|
37
|
+
# SendPing::Events.update("evt_1", { schema: { plan: "string" } })
|
|
38
|
+
def update(event_id, params)
|
|
39
|
+
Client.request(:patch, "/events/#{Client.path_escape(event_id)}", body: params)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# List custom-event definitions. GET /events
|
|
43
|
+
def list(params = {})
|
|
44
|
+
Client.request(:get, "/events", query: Client.pagination(params))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Delete a custom-event definition. DELETE /events/:id
|
|
48
|
+
def delete(event_id)
|
|
49
|
+
Client.request(:delete, "/events/#{Client.path_escape(event_id)}")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SendPing
|
|
4
|
+
# API request logs.
|
|
5
|
+
module Logs
|
|
6
|
+
class << self
|
|
7
|
+
# List logs — cursor pagination plus optional server-side `method`
|
|
8
|
+
# (e.g. "POST") and `status` (e.g. 429) filters. GET /logs
|
|
9
|
+
def list(params = {})
|
|
10
|
+
query = Client.pagination(params)
|
|
11
|
+
method = Client.opt(params, :method)
|
|
12
|
+
status = Client.opt(params, :status)
|
|
13
|
+
query[:method] = method if method
|
|
14
|
+
query[:status] = status unless status.nil?
|
|
15
|
+
Client.request(:get, "/logs", query: query)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Retrieve one log with request/response bodies. GET /logs/:id
|
|
19
|
+
def get(log_id)
|
|
20
|
+
Client.request(:get, "/logs/#{Client.path_escape(log_id)}")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SendPing
|
|
4
|
+
# Read-only results of the in-email poll widget.
|
|
5
|
+
module Polls
|
|
6
|
+
class << self
|
|
7
|
+
# One summary row per email that has poll responses. GET /polls
|
|
8
|
+
def list(params = {})
|
|
9
|
+
Client.request(:get, "/polls", query: Client.pagination(params))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# The aggregated answer breakdown for one email. GET /polls/:email_id
|
|
13
|
+
def get(email_id)
|
|
14
|
+
Client.request(:get, "/polls/#{Client.path_escape(email_id)}")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SendPing
|
|
4
|
+
# Segments are DOMAIN-FIRST: `domain` is required on create and list. Names
|
|
5
|
+
# are unique within a domain (reusable across domains); every domain also
|
|
6
|
+
# carries an auto-created "General" (all contacts) segment.
|
|
7
|
+
module Segments
|
|
8
|
+
class << self
|
|
9
|
+
# POST /segments
|
|
10
|
+
# SendPing::Segments.create({ domain: "yourdomain.com", name: "VIP",
|
|
11
|
+
# filter: { status: "subscribed" } })
|
|
12
|
+
def create(params)
|
|
13
|
+
Client.require_domain!(params, "Segments.create")
|
|
14
|
+
Client.request(:post, "/segments", body: params)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# GET /segments/:id
|
|
18
|
+
def get(segment_id)
|
|
19
|
+
Client.request(:get, "/segments/#{Client.path_escape(segment_id)}")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# List a domain's segments (`domain` required). GET /segments?domain=
|
|
23
|
+
def list(params)
|
|
24
|
+
domain = Client.require_domain!(params, "Segments.list")
|
|
25
|
+
Client.request(:get, "/segments", query: { domain: domain }.merge(Client.pagination(params)))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Preview the contacts a segment currently resolves to (filter matches
|
|
29
|
+
# plus explicit memberships). With no pagination params the response is
|
|
30
|
+
# capped at 1,000 contacts and sets `has_more` — a segment can hold far
|
|
31
|
+
# more than that, so page with `limit` + `after` to read all of it.
|
|
32
|
+
# GET /segments/:id/contacts
|
|
33
|
+
def contacts(segment_id, params = {})
|
|
34
|
+
Client.request(
|
|
35
|
+
:get,
|
|
36
|
+
"/segments/#{Client.path_escape(segment_id)}/contacts",
|
|
37
|
+
query: Client.pagination(params)
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# PATCH /segments/:id
|
|
42
|
+
def update(segment_id, params)
|
|
43
|
+
Client.request(:patch, "/segments/#{Client.path_escape(segment_id)}", body: params)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# DELETE /segments/:id
|
|
47
|
+
def delete(segment_id)
|
|
48
|
+
Client.request(:delete, "/segments/#{Client.path_escape(segment_id)}")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SendPing
|
|
4
|
+
module Templates
|
|
5
|
+
class << self
|
|
6
|
+
# POST /templates — params: { name:, alias:, subject:, html:, text:, variables: [...] }
|
|
7
|
+
def create(params)
|
|
8
|
+
Client.request(:post, "/templates", body: params)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# GET /templates/:id (an alias works anywhere an id is accepted)
|
|
12
|
+
def get(template_id)
|
|
13
|
+
Client.request(:get, "/templates/#{Client.path_escape(template_id)}")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# GET /templates
|
|
17
|
+
def list(params = {})
|
|
18
|
+
Client.request(:get, "/templates", query: Client.pagination(params))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# PATCH /templates/:id
|
|
22
|
+
def update(template_id, params)
|
|
23
|
+
Client.request(:patch, "/templates/#{Client.path_escape(template_id)}", body: params)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Duplicate a template. POST /templates/:id/duplicate — params: { name:, alias: }
|
|
27
|
+
def duplicate(template_id, params = {})
|
|
28
|
+
Client.request(:post, "/templates/#{Client.path_escape(template_id)}/duplicate", body: params)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Publish a template (make its latest draft live). POST /templates/:id/publish
|
|
32
|
+
def publish(template_id)
|
|
33
|
+
Client.request(:post, "/templates/#{Client.path_escape(template_id)}/publish")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# DELETE /templates/:id
|
|
37
|
+
def delete(template_id)
|
|
38
|
+
Client.request(:delete, "/templates/#{Client.path_escape(template_id)}")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SendPing
|
|
4
|
+
# Topics (granular subscriptions) are DOMAIN-FIRST: `domain` is required on
|
|
5
|
+
# create and list. Topic names are reusable across domains.
|
|
6
|
+
module Topics
|
|
7
|
+
class << self
|
|
8
|
+
# POST /topics
|
|
9
|
+
# SendPing::Topics.create({ domain: "yourdomain.com", name: "Product updates",
|
|
10
|
+
# default_subscription: "opt_in" })
|
|
11
|
+
def create(params)
|
|
12
|
+
Client.require_domain!(params, "Topics.create")
|
|
13
|
+
Client.request(:post, "/topics", body: params)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# GET /topics/:id
|
|
17
|
+
def get(topic_id)
|
|
18
|
+
Client.request(:get, "/topics/#{Client.path_escape(topic_id)}")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# List a domain's topics (`domain` required). GET /topics?domain=
|
|
22
|
+
def list(params)
|
|
23
|
+
domain = Client.require_domain!(params, "Topics.list")
|
|
24
|
+
Client.request(:get, "/topics", query: { domain: domain }.merge(Client.pagination(params)))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# PATCH /topics/:id
|
|
28
|
+
def update(topic_id, params)
|
|
29
|
+
Client.request(:patch, "/topics/#{Client.path_escape(topic_id)}", body: params)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# DELETE /topics/:id
|
|
33
|
+
def delete(topic_id)
|
|
34
|
+
Client.request(:delete, "/topics/#{Client.path_escape(topic_id)}")
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|