poodle-ruby 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.
data/README.md ADDED
@@ -0,0 +1,442 @@
1
+ # Poodle Ruby SDK
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/poodle-ruby.svg)](https://badge.fury.io/rb/poodle-ruby)
4
+ [![Build Status](https://github.com/usepoodle/poodle-ruby/workflows/CI/badge.svg)](https://github.com/usepoodle/poodle-ruby/actions)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
6
+
7
+ Ruby SDK for the Poodle's email sending API.
8
+
9
+ ## Table of Contents
10
+
11
+ - [Features](#features)
12
+ - [Installation](#installation)
13
+ - [Quick Start](#quick-start)
14
+ - [Configuration](#configuration)
15
+ - [Usage Examples](#usage-examples)
16
+ - [API Reference](#api-reference)
17
+ - [Error Types](#error-types)
18
+ - [Development](#development)
19
+ - [Contributing](#contributing)
20
+ - [License](#license)
21
+ - [Support](#support)
22
+
23
+ ## Features
24
+
25
+ - ๐Ÿš€ **Simple API** - Send emails with just a few lines of code
26
+ - ๐Ÿ”’ **Type Safe** - Comprehensive validation and error handling
27
+ - ๐ŸŒ **Environment Support** - Easy configuration via environment variables
28
+ - ๐Ÿ“ **Rich Content** - Support for HTML, plain text, and multipart emails
29
+ - ๐Ÿ”„ **Retry Logic** - Built-in support for handling rate limits and network issues
30
+ - ๐Ÿงช **Test Support** - Comprehensive testing utilities and mocks
31
+ - ๐Ÿ“š **Well Documented** - Comprehensive documentation and examples
32
+ - ๐ŸŽฏ **Ruby 3.0+** - Modern Ruby support with keyword arguments
33
+
34
+ ## Installation
35
+
36
+ Add this line to your application's Gemfile:
37
+
38
+ ```ruby
39
+ gem 'poodle-ruby'
40
+ ```
41
+
42
+ And then execute:
43
+
44
+ ```bash
45
+ bundle install
46
+ ```
47
+
48
+ Or install it yourself as:
49
+
50
+ ```bash
51
+ gem install poodle-ruby
52
+ ```
53
+
54
+ ## Quick Start
55
+
56
+ ```ruby
57
+ require 'poodle'
58
+
59
+ # Initialize the client
60
+ client = Poodle::Client.new(api_key: 'your_api_key')
61
+
62
+ # Send an email
63
+ response = client.send(
64
+ from: 'sender@example.com',
65
+ to: 'recipient@example.com',
66
+ subject: 'Hello from Poodle!',
67
+ html: '<h1>Hello World!</h1><p>This email was sent using Poodle.</p>'
68
+ )
69
+
70
+ if response.success?
71
+ puts "Email sent successfully!"
72
+ else
73
+ puts "Failed to send email: #{response.message}"
74
+ end
75
+ ```
76
+
77
+ ## Configuration
78
+
79
+ ### API Key
80
+
81
+ Set your API key in one of these ways:
82
+
83
+ ```ruby
84
+ # 1. Pass directly to client
85
+ client = Poodle::Client.new(api_key: 'your_api_key')
86
+
87
+ # 2. Use environment variable
88
+ ENV['POODLE_API_KEY'] = 'your_api_key'
89
+ client = Poodle::Client.new
90
+
91
+ # 3. Use configuration object
92
+ config = Poodle::Configuration.new(
93
+ api_key: 'your_api_key',
94
+ timeout: 30,
95
+ debug: true
96
+ )
97
+ client = Poodle::Client.new(config)
98
+ ```
99
+
100
+ ### Environment Variables
101
+
102
+ | Variable | Description | Default |
103
+ | ------------------------ | ----------------------------- | --------------------------- |
104
+ | `POODLE_API_KEY` | Your Poodle API key | Required |
105
+ | `POODLE_BASE_URL` | API base URL | `https://api.usepoodle.com` |
106
+ | `POODLE_TIMEOUT` | Request timeout in seconds | `30` |
107
+ | `POODLE_CONNECT_TIMEOUT` | Connection timeout in seconds | `10` |
108
+ | `POODLE_DEBUG` | Enable debug logging | `false` |
109
+
110
+ ## Usage Examples
111
+
112
+ ### Basic Email Sending
113
+
114
+ ```ruby
115
+ # HTML email
116
+ response = client.send_html(
117
+ from: 'newsletter@example.com',
118
+ to: 'subscriber@example.com',
119
+ subject: 'Weekly Newsletter',
120
+ html: '<h1>Newsletter</h1><p>Your weekly update...</p>'
121
+ )
122
+
123
+ # Plain text email
124
+ response = client.send_text(
125
+ from: 'notifications@example.com',
126
+ to: 'user@example.com',
127
+ subject: 'Account Update',
128
+ text: 'Your account has been updated successfully.'
129
+ )
130
+
131
+ # Multipart email (HTML + Text)
132
+ response = client.send(
133
+ from: 'support@example.com',
134
+ to: 'customer@example.com',
135
+ subject: 'Welcome!',
136
+ html: '<h1>Welcome!</h1><p>Thanks for joining us.</p>',
137
+ text: 'Welcome! Thanks for joining us.'
138
+ )
139
+ ```
140
+
141
+ ### Using Email Objects
142
+
143
+ ```ruby
144
+ # Create an Email object for reusability and validation
145
+ email = Poodle::Email.new(
146
+ from: 'sender@example.com',
147
+ to: 'recipient@example.com',
148
+ subject: 'Important Update',
149
+ html: '<h1>Update</h1><p>Please read this important update.</p>',
150
+ text: 'Update: Please read this important update.'
151
+ )
152
+
153
+ # Check email properties
154
+ puts "Multipart email: #{email.multipart?}"
155
+ puts "Content size: #{email.content_size} bytes"
156
+
157
+ # Send the email
158
+ response = client.send_email(email)
159
+ ```
160
+
161
+ ### Multipart Emails (HTML + Text)
162
+
163
+ ```ruby
164
+ # Send emails with both HTML and text content for maximum compatibility
165
+ response = client.send(
166
+ from: 'newsletter@example.com',
167
+ to: 'subscriber@example.com',
168
+ subject: 'Weekly Newsletter',
169
+ html: '<h1>Newsletter</h1><p>This week\'s updates...</p>',
170
+ text: 'Newsletter\n\nThis week\'s updates...'
171
+ )
172
+ ```
173
+
174
+ ### Rails Integration
175
+
176
+ The Poodle SDK provides seamless Rails integration with automatic configuration and helpful rake tasks.
177
+
178
+ #### Installation
179
+
180
+ Add to your Rails application's Gemfile:
181
+
182
+ ```ruby
183
+ gem 'poodle-ruby'
184
+ ```
185
+
186
+ #### Configuration
187
+
188
+ Create an initializer or let Poodle auto-configure:
189
+
190
+ ```ruby
191
+ # config/initializers/poodle.rb
192
+ Poodle::Rails.configure do |config|
193
+ config.api_key = Rails.application.credentials.poodle_api_key
194
+ config.debug = Rails.env.development?
195
+ end
196
+ ```
197
+
198
+ Or use environment variables:
199
+
200
+ ```bash
201
+ # .env or environment
202
+ POODLE_API_KEY=your_api_key_here
203
+ ```
204
+
205
+ #### Usage in Controllers
206
+
207
+ ```ruby
208
+ class NotificationController < ApplicationController
209
+ def send_welcome_email
210
+ response = Poodle::Rails.client.send(
211
+ from: "welcome@example.com",
212
+ to: params[:email],
213
+ subject: "Welcome!",
214
+ html: render_to_string("welcome_email")
215
+ )
216
+
217
+ if response.success?
218
+ render json: { status: "sent" }
219
+ else
220
+ render json: { error: response.message }, status: :unprocessable_entity
221
+ end
222
+ end
223
+ end
224
+ ```
225
+
226
+ #### Rake Tasks
227
+
228
+ ```bash
229
+ # Check configuration
230
+ rake poodle:config
231
+
232
+ # Test connection
233
+ rake poodle:test
234
+
235
+ # Send test email
236
+ rake poodle:send_test[recipient@example.com]
237
+
238
+ # Generate initializer
239
+ rake poodle:install
240
+ ```
241
+
242
+ ### Testing
243
+
244
+ The SDK includes comprehensive testing utilities for easy testing in your applications.
245
+
246
+ #### RSpec Integration
247
+
248
+ ```ruby
249
+ # spec/spec_helper.rb or spec/rails_helper.rb
250
+ require 'poodle'
251
+
252
+ RSpec.configure do |config|
253
+ config.include Poodle::TestHelpers
254
+
255
+ config.before(:each) do
256
+ Poodle.test_mode!
257
+ end
258
+
259
+ config.after(:each) do
260
+ Poodle.clear_deliveries
261
+ end
262
+ end
263
+ ```
264
+
265
+ #### Testing Email Sending
266
+
267
+ ```ruby
268
+ it "sends welcome email" do
269
+ expect {
270
+ UserMailer.send_welcome(user)
271
+ }.to change { Poodle.deliveries.count }.by(1)
272
+
273
+ email = Poodle.last_delivery
274
+ expect(email[:to]).to eq(user.email)
275
+ expect(email[:subject]).to include("Welcome")
276
+ expect(email[:html]).to include(user.name)
277
+ end
278
+
279
+ it "sends notification emails" do
280
+ service.send_notifications
281
+
282
+ assert_email_sent(3)
283
+ assert_email_sent_to("admin@example.com")
284
+ assert_email_sent_with_subject("Alert")
285
+ end
286
+ ```
287
+
288
+ ### Error Handling
289
+
290
+ ```ruby
291
+ begin
292
+ response = client.send(email_data)
293
+ puts "Email sent!" if response.success?
294
+ rescue Poodle::ValidationError => e
295
+ puts "Validation failed: #{e.message}"
296
+ e.errors.each do |field, messages|
297
+ puts "#{field}: #{messages.join(', ')}"
298
+ end
299
+ rescue Poodle::AuthenticationError => e
300
+ puts "Authentication failed: #{e.message}"
301
+ rescue Poodle::RateLimitError => e
302
+ puts "Rate limited. Retry after: #{e.retry_after} seconds"
303
+ rescue Poodle::PaymentError => e
304
+ puts "Payment required: #{e.message}"
305
+ puts "Upgrade at: #{e.upgrade_url}"
306
+ rescue Poodle::ForbiddenError => e
307
+ puts "Access forbidden: #{e.message}"
308
+ rescue Poodle::NetworkError => e
309
+ puts "Network error: #{e.message}"
310
+ rescue Poodle::ServerError => e
311
+ puts "Server error: #{e.message}"
312
+ rescue Poodle::Error => e
313
+ puts "Poodle error: #{e.message}"
314
+ end
315
+ ```
316
+
317
+ ### Retry Logic
318
+
319
+ ```ruby
320
+ def send_with_retry(client, email_data, max_retries: 3)
321
+ retries = 0
322
+
323
+ begin
324
+ client.send(email_data)
325
+ rescue Poodle::RateLimitError => e
326
+ if retries < max_retries && e.retry_after
327
+ retries += 1
328
+ sleep(e.retry_after)
329
+ retry
330
+ else
331
+ raise
332
+ end
333
+ rescue Poodle::NetworkError, Poodle::ServerError => e
334
+ if retries < max_retries
335
+ retries += 1
336
+ sleep(2 ** retries) # Exponential backoff
337
+ retry
338
+ else
339
+ raise
340
+ end
341
+ end
342
+ end
343
+ ```
344
+
345
+ ## API Reference
346
+
347
+ ### Client
348
+
349
+ #### `Poodle::Client.new(config_or_api_key, **options)`
350
+
351
+ Creates a new Poodle client.
352
+
353
+ **Parameters:**
354
+
355
+ - `config_or_api_key` - Configuration object, API key string, or nil
356
+ - `**options` - Additional options (base_url, timeout, debug, etc.)
357
+
358
+ #### `client.send(from:, to:, subject:, html: nil, text: nil)`
359
+
360
+ Sends an email with the specified parameters.
361
+
362
+ #### `client.send_email(email)`
363
+
364
+ Sends an Email object.
365
+
366
+ #### `client.send_html(from:, to:, subject:, html:)`
367
+
368
+ Sends an HTML-only email.
369
+
370
+ #### `client.send_text(from:, to:, subject:, text:)`
371
+
372
+ Sends a text-only email.
373
+
374
+ ### Email
375
+
376
+ #### `Poodle::Email.new(from:, to:, subject:, html: nil, text: nil)`
377
+
378
+ Creates a new Email object with validation.
379
+
380
+ **Methods:**
381
+
382
+ - `#html?` - Returns true if HTML content is present
383
+ - `#text?` - Returns true if text content is present
384
+ - `#multipart?` - Returns true if both HTML and text are present
385
+ - `#content_size` - Returns total content size in bytes
386
+ - `#to_h` - Converts to hash for API requests
387
+
388
+ ### EmailResponse
389
+
390
+ #### Properties
391
+
392
+ - `#success?` - Returns true if email was successfully queued
393
+ - `#failed?` - Returns true if email sending failed
394
+ - `#message` - Response message from API
395
+ - `#data` - Additional response data
396
+
397
+ ### Configuration
398
+
399
+ #### `Poodle::Configuration.new(**options)`
400
+
401
+ Creates a new configuration object.
402
+
403
+ **Options:**
404
+
405
+ - `api_key` - Your Poodle API key
406
+ - `base_url` - API base URL
407
+ - `timeout` - Request timeout in seconds
408
+ - `connect_timeout` - Connection timeout in seconds
409
+ - `debug` - Enable debug logging
410
+ - `http_options` - Additional HTTP client options
411
+
412
+ ## Error Types
413
+
414
+ | Error Class | Description | HTTP Status |
415
+ | ----------------------------- | ------------------------ | ----------- |
416
+ | `Poodle::ValidationError` | Invalid request data | 400, 422 |
417
+ | `Poodle::AuthenticationError` | Invalid API key | 401 |
418
+ | `Poodle::PaymentError` | Payment required | 402 |
419
+ | `Poodle::ForbiddenError` | Access forbidden | 403 |
420
+ | `Poodle::RateLimitError` | Rate limit exceeded | 429 |
421
+ | `Poodle::ServerError` | Server error | 5xx |
422
+ | `Poodle::NetworkError` | Network/connection error | Various |
423
+
424
+ All errors inherit from `Poodle::Error` which provides:
425
+
426
+ - `#message` - Error message
427
+ - `#context` - Additional error context
428
+ - `#status_code` - HTTP status code (if applicable)
429
+
430
+ ## Development
431
+
432
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
433
+
434
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
435
+
436
+ ## Contributing
437
+
438
+ Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details on the process for submitting pull requests and our [Code of Conduct](CODE_OF_CONDUCT.md).
439
+
440
+ ## License
441
+
442
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ task default: %i[]
@@ -0,0 +1,255 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "poodle"
6
+
7
+ # Advanced usage example for the Poodle Ruby SDK
8
+
9
+ # Example 1: Using configuration object
10
+ puts "=== Example 1: Configuration Object ==="
11
+
12
+ config = Poodle::Configuration.new(
13
+ api_key: "your_api_key_here",
14
+ base_url: "https://api.usepoodle.com",
15
+ timeout: 60,
16
+ debug: true
17
+ )
18
+
19
+ client = Poodle::Client.new(config)
20
+ puts "Client initialized with custom configuration"
21
+ puts "User-Agent: #{config.user_agent}"
22
+ puts "Base URL: #{config.base_url}"
23
+
24
+ # Example 2: Using Email objects
25
+ puts "\n=== Example 2: Email Objects ==="
26
+
27
+ begin
28
+ # Create an Email object
29
+ email = Poodle::Email.new(
30
+ from: "newsletter@example.com",
31
+ to: "subscriber@example.com",
32
+ subject: "Weekly Newsletter - #{Date.today}",
33
+ html: <<~HTML,
34
+ <html>
35
+ <body>
36
+ <h1>Weekly Newsletter</h1>
37
+ <p>Welcome to our weekly newsletter!</p>
38
+ <ul>
39
+ <li>Feature 1: New dashboard</li>
40
+ <li>Feature 2: Improved performance</li>
41
+ <li>Feature 3: Bug fixes</li>
42
+ </ul>
43
+ <p>Best regards,<br>The Team</p>
44
+ </body>
45
+ </html>
46
+ HTML
47
+ text: <<~TEXT
48
+ Weekly Newsletter - #{Date.today}
49
+
50
+ Welcome to our weekly newsletter!
51
+
52
+ - Feature 1: New dashboard
53
+ - Feature 2: Improved performance
54
+ - Feature 3: Bug fixes
55
+
56
+ Best regards,
57
+ The Team
58
+ TEXT
59
+ )
60
+
61
+ puts "Email object created:"
62
+ puts "- From: #{email.from}"
63
+ puts "- To: #{email.to}"
64
+ puts "- Subject: #{email.subject}"
65
+ puts "- Has HTML: #{email.html?}"
66
+ puts "- Has Text: #{email.text?}"
67
+ puts "- Is Multipart: #{email.multipart?}"
68
+ puts "- Content Size: #{email.content_size} bytes"
69
+
70
+ # Send the email object
71
+ response = client.send_email(email)
72
+ puts "Email sent: #{response.success?}"
73
+ rescue Poodle::ValidationError => e
74
+ puts "Validation failed: #{e.message}"
75
+ e.errors.each do |field, messages|
76
+ puts " #{field}: #{messages.join(', ')}"
77
+ end
78
+ end
79
+
80
+ # Example 3: Environment variables
81
+ puts "\n=== Example 3: Environment Variables ==="
82
+
83
+ # Set environment variables
84
+ ENV["POODLE_API_KEY"] = "your_api_key_here"
85
+ ENV["POODLE_DEBUG"] = "true"
86
+ ENV["POODLE_TIMEOUT"] = "45"
87
+
88
+ # Create client using environment variables
89
+ env_client = Poodle::Client.new
90
+ puts "Client created using environment variables"
91
+ puts "Debug mode: #{env_client.config.debug?}"
92
+ puts "Timeout: #{env_client.config.timeout} seconds"
93
+
94
+ # Example 4: Multiple content types
95
+ puts "\n=== Example 4: Multiple Content Types ==="
96
+
97
+ begin
98
+ # Send email with both HTML and text content
99
+ multipart_response = client.send(
100
+ from: "newsletter@example.com",
101
+ to: "subscriber@example.com",
102
+ subject: "Weekly Newsletter",
103
+ html: "<h1>Newsletter</h1><p>This week's updates...</p>",
104
+ text: "Newsletter\n\nThis week's updates..."
105
+ )
106
+
107
+ puts "Multipart email sent: #{multipart_response.success?}"
108
+ rescue Poodle::Error => e
109
+ puts "Multipart email error: #{e.message}"
110
+ end
111
+
112
+ # Example 5: Comprehensive error handling
113
+ puts "\n=== Example 5: Error Handling ==="
114
+
115
+ def send_email_with_retry(client, email_data, max_retries: 3)
116
+ retries = 0
117
+
118
+ begin
119
+ response = client.send(email_data)
120
+ puts "Email sent successfully: #{response.message}"
121
+ response
122
+ rescue Poodle::RateLimitError => e
123
+ handle_rate_limit_error(e, retries, max_retries)
124
+ retries += 1
125
+ retry
126
+ rescue Poodle::NetworkError => e
127
+ handle_network_error(e, retries, max_retries)
128
+ retries += 1
129
+ retry
130
+ rescue Poodle::ServerError => e
131
+ handle_server_error(e, retries, max_retries)
132
+ retries += 1
133
+ retry
134
+ rescue Poodle::ValidationError => e
135
+ handle_validation_error(e)
136
+ rescue Poodle::AuthenticationError => e
137
+ handle_authentication_error(e)
138
+ rescue Poodle::PaymentError => e
139
+ handle_payment_error(e)
140
+ rescue Poodle::ForbiddenError => e
141
+ handle_forbidden_error(e)
142
+ end
143
+ end
144
+
145
+ def handle_rate_limit_error(error, retries, max_retries)
146
+ if retries < max_retries && error.retry_after
147
+ puts "Rate limited. Retrying in #{error.retry_after} seconds... (attempt #{retries + 1}/#{max_retries})"
148
+ sleep(error.retry_after)
149
+ else
150
+ puts "Rate limit exceeded. Max retries reached."
151
+ raise error
152
+ end
153
+ end
154
+
155
+ def handle_network_error(error, retries, max_retries)
156
+ if retries < max_retries
157
+ puts "Network error. Retrying... (attempt #{retries + 1}/#{max_retries})"
158
+ sleep(2**(retries + 1)) # Exponential backoff
159
+ else
160
+ puts "Network error. Max retries reached."
161
+ raise error
162
+ end
163
+ end
164
+
165
+ def handle_server_error(error, retries, max_retries)
166
+ if retries < max_retries
167
+ puts "Server error. Retrying... (attempt #{retries + 1}/#{max_retries})"
168
+ sleep(2**(retries + 1))
169
+ else
170
+ puts "Server error. Max retries reached."
171
+ raise error
172
+ end
173
+ end
174
+
175
+ def handle_validation_error(error)
176
+ puts "Validation error (not retryable): #{error.message}"
177
+ puts "Errors: #{error.errors}"
178
+ raise error
179
+ end
180
+
181
+ def handle_authentication_error(error)
182
+ puts "Authentication error (not retryable): #{error.message}"
183
+ raise error
184
+ end
185
+
186
+ def handle_payment_error(error)
187
+ puts "Payment required: #{error.message}"
188
+ puts "Upgrade URL: #{error.upgrade_url}" if error.upgrade_url
189
+ raise error
190
+ end
191
+
192
+ def handle_forbidden_error(error)
193
+ puts "Access forbidden: #{error.message}"
194
+ puts "Reason: #{error.reason}" if error.reason
195
+ raise error
196
+ end
197
+
198
+ # Test the retry mechanism
199
+ email_data = {
200
+ from: "retry@example.com",
201
+ to: "test@example.com",
202
+ subject: "Retry Test",
203
+ html: "<p>Testing retry mechanism</p>"
204
+ }
205
+
206
+ begin
207
+ send_email_with_retry(client, email_data)
208
+ rescue Poodle::Error => e
209
+ puts "Final error: #{e.message}"
210
+ end
211
+
212
+ # Example 6: Batch sending (conceptual)
213
+ puts "\n=== Example 6: Batch Sending ==="
214
+
215
+ recipients = [
216
+ "user1@example.com",
217
+ "user2@example.com",
218
+ "user3@example.com"
219
+ ]
220
+
221
+ successful_sends = 0
222
+ failed_sends = 0
223
+
224
+ recipients.each_with_index do |recipient, index|
225
+ response = client.send(
226
+ from: "batch@example.com",
227
+ to: recipient,
228
+ subject: "Batch Email #{index + 1}",
229
+ html: "<p>Hello #{recipient}! This is batch email ##{index + 1}</p>"
230
+ )
231
+
232
+ if response.success?
233
+ successful_sends += 1
234
+ puts "โœ… Sent to #{recipient}"
235
+ else
236
+ failed_sends += 1
237
+ puts "โŒ Failed to send to #{recipient}: #{response.message}"
238
+ end
239
+
240
+ # Rate limiting: wait between sends
241
+ sleep(0.1)
242
+ rescue Poodle::Error => e
243
+ failed_sends += 1
244
+ puts "โŒ Error sending to #{recipient}: #{e.message}"
245
+ end
246
+
247
+ puts "\nBatch sending complete:"
248
+ puts "Successful: #{successful_sends}"
249
+ puts "Failed: #{failed_sends}"
250
+ puts "Total: #{recipients.length}"
251
+
252
+ # Clean up environment variables
253
+ ENV.delete("POODLE_API_KEY")
254
+ ENV.delete("POODLE_DEBUG")
255
+ ENV.delete("POODLE_TIMEOUT")