zerodrop 0.3.0 → 0.4.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/README.md +18 -4
- data/lib/zerodrop/version.rb +1 -1
- data/lib/zerodrop.rb +47 -5
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e0d9d15ff014c8e68ab17b5df720fe91c106519d0ce80be1b392e6f1a8d6f670
|
|
4
|
+
data.tar.gz: 2976e98c1c12b66f32b0b2dae010f5333c9ae1cc2c8ee82e32e23187242ce56c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e6d6a91f651194b2838800da831b989275d2952ed01158117660f4cc5cf9f97ae64f9d1dfac1807257774466919b9524eeccdb6f0694caaec82eeee74982c245
|
|
7
|
+
data.tar.gz: c239fc035a52eee16bc2e094391f04e27157b4dd9d00e02bea8a87414662d14e8b492f29bc42a68109f788166ea7a6a5e65ffb9e1c5cbfafeba4ff78387b5601
|
data/README.md
CHANGED
|
@@ -141,6 +141,19 @@ mail.generate_inbox
|
|
|
141
141
|
|
|
142
142
|
Reading a `k7x2-…` inbox without your key raises `ZeroDrop::AuthError` with the reason. Without a key, if ZeroDrop held an email because its sender passed the daily limit, the inbox gets a notice instead, and `fetch_latest` and `wait_for_latest` raise `ZeroDrop::HeldError` saying so (0.3.0 or later; older versions return the notice as if it were the email, or time out when a filter skips it). `mail.prefix` holds your prefix if you build an address yourself. A key holding a character that can't be sent in an HTTP header (a line break, a byte-order mark) makes `ZeroDrop::Client.new` raise `ArgumentError`; the message never includes the key.
|
|
143
143
|
|
|
144
|
+
## Workspace domain
|
|
145
|
+
|
|
146
|
+
A Workspace receives mail on a domain of its own, such as `yourcompany-qa.com`, so sign-up forms that block disposable domains accept it. Every inbox on it can be read only with your key. Set the domain as `ZERODROP_DOMAIN`, or pass `domain:`, next to your key:
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
mail = ZeroDrop::Client.new(api_key: ENV["ZERODROP_API_KEY"], domain: "yourcompany-qa.com")
|
|
150
|
+
inbox = mail.generate_inbox
|
|
151
|
+
# => "k7x2-swift-x7k29ab@yourcompany-qa.com"
|
|
152
|
+
email = mail.wait_for_latest(inbox, timeout: 15)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Any address on the domain works, including one you name yourself: mail to `qa@yourcompany-qa.com` is filed under your prefix, and `mail.wait_for_latest("qa@yourcompany-qa.com")` reads it from there. `mail.domain` holds the domain in use. Workspaces are set up with the founder: [zerodrop.dev/pricing](https://zerodrop.dev/pricing).
|
|
156
|
+
|
|
144
157
|
## Parallel test runs
|
|
145
158
|
|
|
146
159
|
`generate_inbox` runs locally — no network request, no collisions:
|
|
@@ -173,7 +186,7 @@ end
|
|
|
173
186
|
## Workspaces
|
|
174
187
|
|
|
175
188
|
```ruby
|
|
176
|
-
mail = ZeroDrop::Client.new(api_key: ENV["ZERODROP_API_KEY"])
|
|
189
|
+
mail = ZeroDrop::Client.new(api_key: ENV["ZERODROP_API_KEY"], domain: "yourcompany-qa.com")
|
|
177
190
|
```
|
|
178
191
|
|
|
179
192
|
## Self-hosted
|
|
@@ -186,9 +199,10 @@ mail = ZeroDrop::Client.new(base_url: "https://your-instance.yourdomain.com")
|
|
|
186
199
|
|
|
187
200
|
| Method | Description |
|
|
188
201
|
|---|---|
|
|
189
|
-
| `Client.new(api_key: nil, base_url:
|
|
202
|
+
| `Client.new(api_key: nil, base_url: ..., domain: nil)` | Create a client. `api_key` defaults to `ENV["ZERODROP_API_KEY"]`; without a key, inboxes are public to anyone who knows the name. `domain` is your Workspace's own domain; it defaults to `ENV["ZERODROP_DOMAIN"]`, then the shared `zerodrop-sandbox.online`, and a value that isn't a domain name raises `ArgumentError`. |
|
|
190
203
|
| `#generate_inbox` | Instant inbox address. No network request. With a key, the name starts with your prefix. |
|
|
191
204
|
| `#prefix` | Your key's inbox prefix (`"k7x2"`), or `nil` without a key. |
|
|
205
|
+
| `#domain` | The domain generated addresses use: your Workspace's, or `zerodrop-sandbox.online`. |
|
|
192
206
|
| `#fetch_latest(inbox, filter: nil)` | Latest matching email or nil. Raises `HeldError` if nothing matches and the inbox has a notice that ZeroDrop held an email. |
|
|
193
207
|
| `#wait_for_latest(inbox, timeout: 10, poll_interval: 2, filter: nil)` | Block until email arrives. Raises `TimeoutError` on timeout, and `HeldError` at once if ZeroDrop held the email. |
|
|
194
208
|
|
|
@@ -200,8 +214,8 @@ mail = ZeroDrop::Client.new(base_url: "https://your-instance.yourdomain.com")
|
|
|
200
214
|
| OTP auto-extraction | ✓ | ✓ |
|
|
201
215
|
| Magic link extraction | ✓ | ✓ |
|
|
202
216
|
| Email filtering | ✓ | ✓ |
|
|
203
|
-
| Email retention | 30 min |
|
|
204
|
-
| Custom domains | ✗ | ✓ |
|
|
217
|
+
| Email retention | 30 min | Up to 7 days, set for your team |
|
|
218
|
+
| Custom domains | ✗ | ✓ (see [Workspace domain](#workspace-domain)) |
|
|
205
219
|
| API key | ✓ free at [zerodrop.dev/keys](https://zerodrop.dev/keys) | ✓ |
|
|
206
220
|
| Daily limit per sender | 5 inboxes without a key, none with one | None |
|
|
207
221
|
|
data/lib/zerodrop/version.rb
CHANGED
data/lib/zerodrop.rb
CHANGED
|
@@ -34,6 +34,9 @@ module ZeroDrop
|
|
|
34
34
|
# marks that copying and pasting can bring along.
|
|
35
35
|
KEY_PADDING = /\A[[:space:]\0\u{FEFF}]+|[[:space:]\0\u{FEFF}]+\z/
|
|
36
36
|
|
|
37
|
+
# A Workspace's own domain, from the domain: option or ZERODROP_DOMAIN.
|
|
38
|
+
HOST_NAME = /\A(?=.{1,253}\z)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{2,63}|xn--[a-z0-9-]{1,59})\z/
|
|
39
|
+
|
|
37
40
|
# Raised when no email arrives within the timeout.
|
|
38
41
|
class TimeoutError < StandardError
|
|
39
42
|
def initialize(inbox, timeout)
|
|
@@ -104,13 +107,22 @@ module ZeroDrop
|
|
|
104
107
|
# Your key's inbox prefix ("k7x2"), or nil without a key.
|
|
105
108
|
attr_reader :prefix
|
|
106
109
|
|
|
110
|
+
# The domain generated addresses use: your Workspace's, or
|
|
111
|
+
# zerodrop-sandbox.online.
|
|
112
|
+
attr_reader :domain
|
|
113
|
+
|
|
107
114
|
# @param api_key [String, nil] Free API key from https://zerodrop.dev/keys.
|
|
108
115
|
# Defaults to ENV["ZERODROP_API_KEY"]. Without a key, anyone who knows
|
|
109
116
|
# an inbox's name can read it.
|
|
110
117
|
# @param base_url [String] Override for self-hosted instances.
|
|
118
|
+
# @param domain [String, nil] Your Workspace's own domain, such as
|
|
119
|
+
# "yourcompany-qa.com". Generated addresses use it, and its inboxes are
|
|
120
|
+
# read with your key. Defaults to ENV["ZERODROP_DOMAIN"], then the shared
|
|
121
|
+
# zerodrop-sandbox.online.
|
|
111
122
|
# @raise [ArgumentError] if the key holds a character that can't be sent in
|
|
112
|
-
# an HTTP header (a line break, a byte-order mark...)
|
|
113
|
-
|
|
123
|
+
# an HTTP header (a line break, a byte-order mark...), or if the domain
|
|
124
|
+
# isn't a domain name
|
|
125
|
+
def initialize(api_key: nil, base_url: DEFAULT_BASE_URL, domain: nil)
|
|
114
126
|
key = trim_key(api_key)
|
|
115
127
|
key = trim_key(ENV.fetch("ZERODROP_API_KEY", "")) if key.empty?
|
|
116
128
|
unless key.empty? || key.match?(KEY_CHARS)
|
|
@@ -120,6 +132,12 @@ module ZeroDrop
|
|
|
120
132
|
@api_key = key.empty? ? nil : key
|
|
121
133
|
@prefix = KEY_FORMAT.match(@api_key.to_s)&.[](1)
|
|
122
134
|
@base_url = base_url.chomp("/")
|
|
135
|
+
@domain = resolve_domain(domain)
|
|
136
|
+
|
|
137
|
+
if @domain != FREE_DOMAIN && @prefix.nil?
|
|
138
|
+
warn "[ZeroDrop] Inboxes on #{@domain} belong to your Workspace and can be read only with its API key " \
|
|
139
|
+
"(zd_xxxx_… from https://zerodrop.dev/keys). Set ZERODROP_API_KEY."
|
|
140
|
+
end
|
|
123
141
|
end
|
|
124
142
|
|
|
125
143
|
# Returns a ready-to-use email address instantly.
|
|
@@ -132,7 +150,7 @@ module ZeroDrop
|
|
|
132
150
|
suffix = SecureRandom.alphanumeric(7).downcase
|
|
133
151
|
name = "#{adjective}-#{suffix}"
|
|
134
152
|
name = "#{@prefix}-#{name}" if @prefix
|
|
135
|
-
"#{name}@#{
|
|
153
|
+
"#{name}@#{@domain}"
|
|
136
154
|
end
|
|
137
155
|
|
|
138
156
|
# Returns the newest email matching the filter, or nil.
|
|
@@ -208,6 +226,22 @@ module ZeroDrop
|
|
|
208
226
|
value.to_s.b.strip
|
|
209
227
|
end
|
|
210
228
|
|
|
229
|
+
# The receiving domain: the option, else ZERODROP_DOMAIN, else the shared
|
|
230
|
+
# one. Trimmed like the key, since it is copied and pasted the same way.
|
|
231
|
+
def resolve_domain(option)
|
|
232
|
+
value = trim_key(option)
|
|
233
|
+
value = trim_key(ENV.fetch("ZERODROP_DOMAIN", "")) if value.empty?
|
|
234
|
+
return FREE_DOMAIN if value.empty?
|
|
235
|
+
|
|
236
|
+
domain = value.downcase.delete_suffix(".")
|
|
237
|
+
unless domain.match?(HOST_NAME)
|
|
238
|
+
raise ArgumentError, "ZeroDrop: the domain: option (or ZERODROP_DOMAIN) isn't a domain name. " \
|
|
239
|
+
"Use your Workspace's domain, such as yourcompany-qa.com."
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
domain
|
|
243
|
+
end
|
|
244
|
+
|
|
211
245
|
def http_get(uri)
|
|
212
246
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
213
247
|
http.use_ssl = uri.scheme == "https"
|
|
@@ -269,9 +303,17 @@ module ZeroDrop
|
|
|
269
303
|
body.length > 5000 ? body[0, 5000] : body
|
|
270
304
|
end
|
|
271
305
|
|
|
306
|
+
# The inbox an address is read from: its local part. On your Workspace's
|
|
307
|
+
# own domain, mail is filed under your key's prefix, so an address you
|
|
308
|
+
# named yourself, such as qa@yourcompany-qa.com, is read as k7x2-qa.
|
|
272
309
|
def inbox_name(inbox)
|
|
273
|
-
|
|
274
|
-
|
|
310
|
+
local = inbox.partition("@").first.downcase
|
|
311
|
+
return local unless inbox.include?("@") && @prefix && @domain != FREE_DOMAIN
|
|
312
|
+
|
|
313
|
+
domain = inbox.rpartition("@").last.downcase.delete_suffix(".")
|
|
314
|
+
return local if domain != @domain || local.start_with?("#{@prefix}-")
|
|
315
|
+
|
|
316
|
+
"#{@prefix}-#{local}"
|
|
275
317
|
end
|
|
276
318
|
end
|
|
277
319
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: zerodrop
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ZeroDrop
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-11-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: ZeroDrop catches verification emails at Cloudflare's edge and auto-extracts
|
|
14
14
|
OTP codes and magic links. Test email verification, magic links, OTP and password
|