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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0225cad7779948af748c4fbb893f2b99fa91d3e5847229e8328ce435976ab9f6
4
- data.tar.gz: df8926330db49fe3693dd70c4fdb19020314151fb5f8cdec2d00af83f0418b9f
3
+ metadata.gz: e0d9d15ff014c8e68ab17b5df720fe91c106519d0ce80be1b392e6f1a8d6f670
4
+ data.tar.gz: 2976e98c1c12b66f32b0b2dae010f5333c9ae1cc2c8ee82e32e23187242ce56c
5
5
  SHA512:
6
- metadata.gz: a2e94b76e3b4be8886b8da78537cdec7754f03c012c592b24fe870d0abb7b8cf44f70fc8d89b5e9eb5830126536dad0645c0323a06e5b3416fb44f2a685e7cc8
7
- data.tar.gz: c626edc88d22b88b74345fa45740f002532b73078df16c9ca9cf7ce067c2ffce0352031542a01255a10057e23d39670a6476080d1ffd2d616e4b9c791e410505
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: ...)` | Create a client. `api_key` defaults to `ENV["ZERODROP_API_KEY"]`; without a key, inboxes are public to anyone who knows the name. |
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 | Extended |
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ZeroDrop
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
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
- def initialize(api_key: nil, base_url: DEFAULT_BASE_URL)
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}@#{FREE_DOMAIN}"
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
- name = inbox.include?("@") ? inbox.split("@").first : inbox
274
- name.downcase
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.3.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-10-23 00:00:00.000000000 Z
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