sendclipper 0.1.0 → 0.2.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/CHANGELOG.md +6 -0
- data/README.md +17 -0
- data/lib/sendclipper/resources/base.rb +37 -1
- data/lib/sendclipper/resources/emails.rb +10 -2
- data/lib/sendclipper/resources/sandboxes.rb +3 -2
- data/lib/sendclipper/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4044288ac0d8d8dddaa8e914c9ee649cf24d31043c6135360788ea7fccb7522f
|
|
4
|
+
data.tar.gz: f3cf0b37bb3c1e641e7ec4121c580a186c39003dadd2e308d540008a7d8bb89c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 822f264d7447e5d43e2280a65b04792f8c317507b26a30f77dc4892995c6f28a1cacd5c1884cf7b548918616271acb4aac9b38d621eba3219c567ef70efbb867
|
|
7
|
+
data.tar.gz: 68f7d996faf5e10b92f91a25babcb7e60fc37d07632d5a5e4c38c2887a0c5fcbef08042528596bab1fd051f2dee871be1eecdd0e32cf2eabc41f8665ed079a0a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0 — 2026-10-18
|
|
4
|
+
|
|
5
|
+
- `emails.send`, `sandboxes.send` and each `batch.send` item take `reply_to:` (the API's `replyTo`): one address or a list of up to 10, sent as the `Reply-To` header.
|
|
6
|
+
- `emails.send` and `sandboxes.send` take `attachments:`, a list of `{ filename:, content:, content_type: }`. `content:` is the file's raw bytes (`File.binread("invoice.pdf")`) and the SDK base64-encodes it; pass `content_base64:` instead when you already hold base64, and it is sent as it is. Giving both, or a `content:` that is not a String, raises `ArgumentError` before any request. Limits: 10 MiB per file, 25 MiB per message, 20 files. A batch item with attachments is refused by the API (`attachments_not_batchable`).
|
|
7
|
+
- Needs the SendClipper API from 2026-10-18 on; an older API ignores both fields.
|
|
8
|
+
|
|
3
9
|
## 0.1.0 — 2026-10-16
|
|
4
10
|
|
|
5
11
|
First release.
|
data/README.md
CHANGED
|
@@ -43,6 +43,23 @@ Fields are keyword arguments (or a Hash). Top-level names may be snake_case (`te
|
|
|
43
43
|
|
|
44
44
|
`from` must be a bare address on a domain you verified in SendClipper. Each message has one recipient in `to`; for several, use `batch.send`.
|
|
45
45
|
|
|
46
|
+
## Reply-To and attachments
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
data, error = client.emails.send(
|
|
50
|
+
from: "bookings@acme.io",
|
|
51
|
+
to: "ada@lumen.io",
|
|
52
|
+
subject: "Your booking",
|
|
53
|
+
html: "<p>Your confirmation is attached.</p>",
|
|
54
|
+
reply_to: "support@acme.io",
|
|
55
|
+
attachments: [
|
|
56
|
+
{ filename: "booking-42.pdf", content: File.binread("booking-42.pdf") }
|
|
57
|
+
]
|
|
58
|
+
)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`reply_to:` is one address or a list of up to 10. `content:` is the file's raw bytes (read it with `File.binread`) and the SDK base64-encodes it; if you already hold base64, pass `content_base64:` instead and it is sent as it is. `content_type:` is guessed from the filename when you leave it out. Limits: 10 MiB per file, 25 MiB of files per message, and your sending provider's own. Attachments are not accepted on a batch item (it fails with `attachments_not_batchable`); send those emails on their own. `sandboxes.send` takes the same fields.
|
|
62
|
+
|
|
46
63
|
## Send with a template
|
|
47
64
|
|
|
48
65
|
Design and publish a template in the dashboard, then send it by id or by its send name:
|
|
@@ -29,7 +29,43 @@ module SendClipper
|
|
|
29
29
|
fields.each_with_object({}) do |(k, v), out|
|
|
30
30
|
next if v.nil?
|
|
31
31
|
|
|
32
|
-
out[k
|
|
32
|
+
out[camel(k)] = v
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def camel(key) = key.to_s.gsub(/_([a-z0-9])/) { Regexp.last_match(1).upcase }
|
|
37
|
+
|
|
38
|
+
# The fields of one message (emails.send, sandboxes.send, a batch item):
|
|
39
|
+
# camelized, with each attachment's +content:+ (the file's raw bytes)
|
|
40
|
+
# base64-encoded. +content_base64:+ is content that is already base64,
|
|
41
|
+
# sent as it is.
|
|
42
|
+
def email_fields(fields)
|
|
43
|
+
out = camelize(fields)
|
|
44
|
+
list = out["attachments"]
|
|
45
|
+
out["attachments"] = list.map { |a| attachment_field(a) } if list.is_a?(Array)
|
|
46
|
+
out
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def attachment_field(attachment)
|
|
50
|
+
given = attachment.to_h
|
|
51
|
+
names = given.reject { |_, v| v.nil? }.keys.map(&:to_s)
|
|
52
|
+
if names.include?("content") && (names & %w[content_base64 contentBase64]).any?
|
|
53
|
+
raise ArgumentError, "An attachment takes content: (the file's bytes) or content_base64:, not both"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
given.each_with_object({}) do |(k, v), out|
|
|
57
|
+
next if v.nil?
|
|
58
|
+
|
|
59
|
+
case k.to_s
|
|
60
|
+
when "content"
|
|
61
|
+
unless v.is_a?(String)
|
|
62
|
+
raise ArgumentError, "An attachment's content: must be a String of the file's bytes, e.g. File.binread(path)"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
out["content"] = [v].pack("m0")
|
|
66
|
+
when "content_base64", "contentBase64" then out["content"] = v
|
|
67
|
+
else out[camel(k)] = v
|
|
68
|
+
end
|
|
33
69
|
end
|
|
34
70
|
end
|
|
35
71
|
|
|
@@ -10,9 +10,14 @@ module SendClipper
|
|
|
10
10
|
#
|
|
11
11
|
# Every call carries an Idempotency-Key, so the SDK's own retries never
|
|
12
12
|
# send twice. Pass +idempotency_key:+ to make YOUR retries safe too.
|
|
13
|
+
#
|
|
14
|
+
# +reply_to:+ is one address or a list of up to 10. +attachments:+ is a
|
|
15
|
+
# list of { filename:, content:, content_type: }, where +content:+ is the
|
|
16
|
+
# file's raw bytes (File.binread("invoice.pdf")) and the SDK base64-encodes
|
|
17
|
+
# it; pass +content_base64:+ instead when you already hold base64.
|
|
13
18
|
def send(params = nil, **kw)
|
|
14
19
|
body, options = split(params, kw)
|
|
15
|
-
api_post("/emails", body, options: options)
|
|
20
|
+
api_post("/emails", email_fields(body), options: options)
|
|
16
21
|
end
|
|
17
22
|
|
|
18
23
|
# Same as client.batch.send.
|
|
@@ -50,11 +55,14 @@ module SendClipper
|
|
|
50
55
|
class Batch < Base
|
|
51
56
|
# Up to 500 messages in one request (one recipient each). Every item is
|
|
52
57
|
# accepted or refused on its own; a refused item costs its siblings nothing.
|
|
58
|
+
# An item takes +reply_to:+; an item with +attachments:+ is refused
|
|
59
|
+
# (+attachments_not_batchable+), so send those messages on their own.
|
|
53
60
|
def send(emails, **options) = self.class.post(@http, emails, options)
|
|
54
61
|
|
|
55
62
|
# @api private
|
|
56
63
|
def self.post(http, emails, options)
|
|
57
|
-
|
|
64
|
+
fields = new(http)
|
|
65
|
+
items = emails.map { |e| fields.__send__(:email_fields, e.to_h) }
|
|
58
66
|
http.call("POST", "/emails/batch", body: { "emails" => items }, options: options)
|
|
59
67
|
end
|
|
60
68
|
end
|
|
@@ -38,10 +38,11 @@ module SendClipper
|
|
|
38
38
|
def reset_credentials(id, **options) = api_post("/sandboxes/#{seg(id)}/credentials", nil, options: options)
|
|
39
39
|
|
|
40
40
|
# Send to a sandbox through its JSON door, with the sandbox's own
|
|
41
|
-
# +sc_sbx_...+ token. Same fields as emails.send
|
|
41
|
+
# +sc_sbx_...+ token. Same fields as emails.send (reply_to and attachments
|
|
42
|
+
# included); nothing is delivered.
|
|
42
43
|
def send(sandbox_token, params = nil, **kw)
|
|
43
44
|
body, options = split(params, kw)
|
|
44
|
-
api_post("/sandbox/emails", body, options: options, bearer: sandbox_token)
|
|
45
|
+
api_post("/sandbox/emails", email_fields(body), options: options, bearer: sandbox_token)
|
|
45
46
|
end
|
|
46
47
|
end
|
|
47
48
|
|
data/lib/sendclipper/version.rb
CHANGED