letmesendemail 0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f051881d8ab62e6dfe9b77358ad11248ea80b39c9daabd76f99ec241d82815ef
4
+ data.tar.gz: d27abe88c1782cbfa48ac368e31aa8022495a1181666697c626d1787ede676d7
5
+ SHA512:
6
+ metadata.gz: e22b56818027da8277ffacc91c91b5358e9c9b5a797a554fb6b66c571cfe805135cde03719ddf22e044b895af2c51481569deb5f451a4860a33c653848f17357
7
+ data.tar.gz: cd94300002f8d73c4250da3f14fa3295c4653e01e5f2df2cd3959f3c0386ac4734b694ab90522a8c5642c090dd43d23550551414ba8282019ecc9968e6ffe372
data/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — 2026-07-13
4
+
5
+ - Initial Ruby SDK implementation for emails, domains, contacts, contact categories, and email topics.
6
+ - Structured API, network, timeout, rate-limit, and webhook errors.
7
+ - Validated client configuration, safe resource path encoding, conservative retries, and `Retry-After` support.
8
+ - Constant-time webhook signature verification with ordinary and Rack/CGI header support.
9
+ - Recursive response models with `to_h`, `as_json`, and standard JSON serialization.
10
+ - Fixture-backed tests, runnable examples, CI, comprehensive documentation, and RubyGems release guidance.
11
+ - Webhook verification tests use generic payload data without assuming undocumented
12
+ event names or payload fields.
13
+ - Development dependencies constrain `parallel` to the Ruby 3.1-compatible 1.x
14
+ series so the complete Ruby 3.1–4.0 CI matrix resolves consistently.
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Apsonex Inc. <info@apsonex.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,220 @@
1
+ # letmesend.email SDK for Ruby
2
+
3
+ The official Ruby client for the [letmesend.email](https://letmesend.email/) API. It supports Ruby 3.1+, uses `net/http`, and has no runtime HTTP dependency.
4
+
5
+ ## Installation
6
+
7
+ Add the gem to your bundle:
8
+
9
+ ```bash
10
+ bundle add letmesendemail
11
+ ```
12
+
13
+ Or install it directly:
14
+
15
+ ```bash
16
+ gem install letmesendemail
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```ruby
22
+ require "letmesendemail"
23
+
24
+ api_key = ENV.fetch("LETMESENDEMAIL_API_KEY")
25
+ raise "LETMESENDEMAIL_API_KEY must not be empty" if api_key.empty?
26
+
27
+ client = LetMeSendEmail::Client.new(api_key: api_key)
28
+
29
+ begin
30
+ email = client.emails.send(
31
+ from: "Acme <hello@example.com>",
32
+ to: ["customer@example.net"],
33
+ subject: "Welcome",
34
+ html: "<p>Thanks for joining.</p>",
35
+ text: "Thanks for joining."
36
+ )
37
+ puts email["id"]
38
+ rescue LetMeSendEmail::Error => e
39
+ warn "Email request failed: #{e.message}"
40
+ end
41
+ ```
42
+
43
+ ## Configuration
44
+
45
+ ```ruby
46
+ config = LetMeSendEmail::Config.new(api_key)
47
+ config.base_url = "https://letmesend.email/api/v1"
48
+ config.timeout_ms = 30_000
49
+ config.retries = 2
50
+
51
+ client = LetMeSendEmail::Client.new(config: config)
52
+ ```
53
+
54
+ The default timeout is 30,000 milliseconds and retries default to zero. Retry counts must be between 0 and 20. Safe requests retry network errors, timeouts, HTTP 408/500/502/503/504, and HTTP 429 only when `Retry-After` is valid and no more than 300 seconds. Email sends require an idempotency key to retry; verification calls are never retried.
55
+
56
+ ## Emails
57
+
58
+ ```ruby
59
+ sent = client.emails.send(
60
+ from: "Acme <hello@example.com>",
61
+ to: ["customer@example.net"],
62
+ subject: "Receipt",
63
+ text: "Your receipt is ready.",
64
+ idempotency_key: "receipt-order-123"
65
+ )
66
+
67
+ templated = client.emails.send_with_template(
68
+ from: "Acme <hello@example.com>",
69
+ to: ["customer@example.net"],
70
+ template_id: "template_123",
71
+ template_variables: [
72
+ { key: "CUSTOMER_NAME", type: "string", value: "Taylor" },
73
+ { key: "ORDER_NUMBER", type: "number", value: 12_345 }
74
+ ]
75
+ )
76
+
77
+ verification = client.emails.verify("customer@example.net")
78
+ email = client.emails.get("email_123")
79
+ ```
80
+
81
+ ## Domains
82
+
83
+ ```ruby
84
+ domains = client.domains.list(per_page: 20)
85
+ domain = client.domains.get("domain_123")
86
+ verification = client.domains.verify("example.com")
87
+ ```
88
+
89
+ ## Contacts
90
+
91
+ ```ruby
92
+ contact = client.contacts.create(
93
+ email: "customer@example.net",
94
+ first_name: "Taylor",
95
+ categories: ["category_123"]
96
+ )
97
+
98
+ client.contacts.update(contact["id"], first_name: "Morgan")
99
+ client.contacts.get(contact["id"])
100
+ client.contacts.list(per_page: 20)
101
+ client.contacts.delete(contact["id"])
102
+ ```
103
+
104
+ ## Contact Categories
105
+
106
+ ```ruby
107
+ category = client.contact_categories.create(name: "Customers")
108
+ client.contact_categories.get(category["id"])
109
+ client.contact_categories.list(per_page: 20)
110
+ client.contact_categories.update(category["id"], name: "Active customers")
111
+ client.contact_categories.delete(category["id"])
112
+ ```
113
+
114
+ ## Email Topics
115
+
116
+ ```ruby
117
+ topic = client.email_topics.create(
118
+ name: "Product updates",
119
+ slug: "product-updates",
120
+ public: true,
121
+ auto_subscribe: false
122
+ )
123
+ client.email_topics.get(topic["id"])
124
+ client.email_topics.list(per_page: 20)
125
+ client.email_topics.update(topic["id"], description: "Product news")
126
+ client.email_topics.delete(topic["id"])
127
+ ```
128
+
129
+ ## Pagination
130
+
131
+ All list resources accept `per_page`, `after`, and `before`. Do not send `after` and `before` together.
132
+
133
+ ```ruby
134
+ first = client.emails.list(per_page: 20)
135
+ items = first["data"]
136
+
137
+ if first["pagination"]["has_more"] && !items.empty?
138
+ next_page = client.emails.list(per_page: 20, after: items.last["id"])
139
+ end
140
+
141
+ unless items.empty?
142
+ previous_page = client.emails.list(per_page: 20, before: items.first["id"])
143
+ end
144
+ ```
145
+
146
+ Retain cursor history in your application for Back/Previous navigation. The API does not provide arbitrary page-number jumps.
147
+
148
+ ## Model Serialization
149
+
150
+ API operations return `LetMeSendEmail::Models::Model`. It supports hash-style access, recursive `to_h`, Rails-compatible `as_json`, and standard JSON encoding.
151
+
152
+ ```ruby
153
+ contact = client.contacts.get("contact_123")
154
+ database_attributes = contact.to_h
155
+ json = JSON.generate(contact)
156
+ ```
157
+
158
+ `to_h` returns a defensive recursive copy. It preserves API field names and `nil` values while excluding client configuration, credentials, and transport state.
159
+
160
+ ## Error Handling
161
+
162
+ ```ruby
163
+ begin
164
+ client.emails.get("email_123")
165
+ rescue LetMeSendEmail::ValidationError => e
166
+ warn e.validation_errors.inspect
167
+ rescue LetMeSendEmail::AuthenticationError, LetMeSendEmail::AuthorizationError => e
168
+ warn e.message
169
+ rescue LetMeSendEmail::RateLimitError => e
170
+ warn "Retry after #{e.retry_after.inspect} seconds"
171
+ rescue LetMeSendEmail::NetworkError, LetMeSendEmail::TimeoutError => e
172
+ warn e.message
173
+ rescue LetMeSendEmail::Error => e
174
+ warn "#{e.message}; request_id=#{e.request_id.inspect}"
175
+ end
176
+ ```
177
+
178
+ ## Webhooks
179
+
180
+ Always verify the exact, unmodified request body before using the parsed payload.
181
+
182
+ ```ruby
183
+ require "json"
184
+
185
+ secret = ENV.fetch("LETMESENDEMAIL_WEBHOOK_SECRET")
186
+ raise "LETMESENDEMAIL_WEBHOOK_SECRET must not be empty" if secret.empty?
187
+ raw_request_body = $stdin.read
188
+ request_headers = JSON.parse(ENV.fetch("LETMESENDEMAIL_WEBHOOK_HEADERS_JSON"))
189
+
190
+ begin
191
+ payload = LetMeSendEmail::Webhooks.verify(raw_request_body, request_headers, secret)
192
+ puts payload.inspect
193
+ rescue LetMeSendEmail::WebhookVerificationError,
194
+ LetMeSendEmail::WebhookSigningError => e
195
+ warn "Webhook rejected: #{e.message}"
196
+ end
197
+ ```
198
+
199
+ The verifier accepts ordinary headers and Rack/CGI `HTTP_WEBHOOK_*` keys. The default timestamp tolerance is 300 seconds.
200
+
201
+ ## Testing
202
+
203
+ The SDK does not make network requests until a resource method is called. Use application-level dependency boundaries around the client in tests. To work on the gem:
204
+
205
+ ```bash
206
+ bundle exec rubocop
207
+ bundle exec rspec
208
+ ```
209
+
210
+ ## Version Support
211
+
212
+ Ruby 3.1, 3.2, 3.3, 3.4, and 4.0 are tested in CI.
213
+
214
+ ## Changelog
215
+
216
+ See the [changelog](CHANGELOG.md).
217
+
218
+ ## Full Documentation
219
+
220
+ See the [complete Ruby SDK manual](docs/docs.md).