laneful-ruby 1.0.1

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.
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'laneful'
5
+
6
+ # Simple example demonstrating basic usage of the Laneful Ruby SDK
7
+ puts 'šŸ“§ Laneful Ruby SDK - Simple Example'
8
+ puts "====================================\n"
9
+
10
+ # Configuration - Replace with your actual credentials
11
+ base_url = 'https://your-endpoint.send.laneful.net'
12
+ auth_token = 'your-auth-token'
13
+
14
+ # Email addresses - Replace with your actual addresses
15
+ sender_email = 'sender@yourdomain.com'
16
+ sender_name = 'Your Name'
17
+ recipient_email = 'recipient@example.com'
18
+ recipient_name = 'Recipient Name'
19
+ cc_email = 'cc@example.com'
20
+ cc_name = 'CC Recipient'
21
+
22
+ # Create client
23
+ begin
24
+ client = Laneful::Client.new(base_url, auth_token)
25
+ puts 'āœ… Client created successfully'
26
+ rescue Laneful::ValidationException => e
27
+ puts "āŒ Failed to create client: #{e.message}"
28
+ puts 'Please check your base_url and auth_token configuration.'
29
+ exit 1
30
+ end
31
+
32
+ # Test 1: Send a simple text email
33
+ puts "\nšŸ“ Test 1: Sending Simple Text Email"
34
+ puts '------------------------------------'
35
+
36
+ begin
37
+ email = Laneful::Email::Builder.new
38
+ .from(Laneful::Address.new(sender_email, sender_name))
39
+ .to(Laneful::Address.new(recipient_email, recipient_name))
40
+ .subject('Hello from Laneful Ruby SDK! šŸš€')
41
+ .text_content('Hi! This is a test email sent using the Laneful Ruby SDK. ' \
42
+ 'The SDK is working perfectly!')
43
+ .tag('ruby-sdk-test')
44
+ .build
45
+
46
+ response = client.send_email(email)
47
+ puts 'āœ… Simple email sent successfully!'
48
+ puts "Response: #{response}"
49
+ rescue Laneful::ValidationException => e
50
+ puts "āŒ Validation error: #{e.message}"
51
+ rescue Laneful::ApiException => e
52
+ puts "āŒ API error: #{e.message} (Status: #{e.status_code})"
53
+ rescue Laneful::HttpException => e
54
+ puts "āŒ HTTP error: #{e.message} (Status: #{e.status_code})"
55
+ end
56
+
57
+ # Test 2: Send an HTML email with CC
58
+ puts "\nšŸŽØ Test 2: Sending HTML Email with CC"
59
+ puts '-------------------------------------'
60
+
61
+ begin
62
+ tracking = Laneful::TrackingSettings.new(opens: true, clicks: true, unsubscribes: false)
63
+
64
+ email = Laneful::Email::Builder.new
65
+ .from(Laneful::Address.new(sender_email, sender_name))
66
+ .to(Laneful::Address.new(recipient_email, recipient_name))
67
+ .cc(Laneful::Address.new(cc_email, cc_name))
68
+ .subject('Ruby SDK Test - HTML Email with Tracking šŸ“§')
69
+ .html_content(<<~HTML)
70
+ <!DOCTYPE html>
71
+ <html>
72
+ <head>
73
+ <title>Ruby SDK Test</title>
74
+ </head>
75
+ <body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px;">
76
+ <h1 style="color: #2c3e50;">šŸŽ‰ Ruby SDK Test Successful!</h1>
77
+ <p>Hello! This email was sent using the <strong>Laneful Ruby SDK</strong> with modern Ruby features:</p>
78
+ <ul style="background-color: #f8f9fa; padding: 15px; border-radius: 5px;">
79
+ <li>āœ… Frozen string literals for immutability</li>
80
+ <li>āœ… Keyword arguments for clean APIs</li>
81
+ <li>āœ… Safe navigation operator (&.)</li>
82
+ <li>āœ… Enhanced exception handling</li>
83
+ <li>āœ… Modern Ruby syntax throughout</li>
84
+ </ul>
85
+ <p style="color: #7f8c8d;">This email has tracking enabled for opens and clicks.</p>
86
+ <hr style="border: none; border-top: 1px solid #eee; margin: 20px 0;">
87
+ <p style="font-size: 12px; color: #95a5a6;">
88
+ Sent from: #{sender_email}<br>
89
+ To: #{recipient_email}<br>
90
+ CC: #{cc_email}
91
+ </p>
92
+ </body>
93
+ </html>
94
+ HTML
95
+ .text_content('Ruby SDK Test Successful! Hello! This email was sent using ' \
96
+ 'the Laneful Ruby SDK with modern Ruby features including ' \
97
+ 'frozen string literals, keyword arguments, safe navigation ' \
98
+ 'operator, and enhanced exception handling. This email has ' \
99
+ 'tracking enabled for opens and clicks.')
100
+ .tracking(tracking)
101
+ .tag('html-cc-test')
102
+ .build
103
+
104
+ response = client.send_email(email)
105
+ puts 'āœ… HTML email with CC sent successfully!'
106
+ puts "Response: #{response}"
107
+ rescue Laneful::ValidationException => e
108
+ puts "āŒ Validation error: #{e.message}"
109
+ rescue Laneful::ApiException => e
110
+ puts "āŒ API error: #{e.message} (Status: #{e.status_code})"
111
+ rescue Laneful::HttpException => e
112
+ puts "āŒ HTTP error: #{e.message} (Status: #{e.status_code})"
113
+ end
114
+
115
+ puts "\nšŸŽ‰ Tests completed!"
116
+ puts "\nšŸ“‹ Summary:"
117
+ puts ' • Ruby SDK is working correctly'
118
+ puts ' • Real API endpoint is accessible'
119
+ puts ' • Email building and sending functionality is operational'
120
+ puts ' • Error handling is working properly'
121
+ puts "\nšŸ’” Check your email inboxes for the test emails!"
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/laneful/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'laneful-ruby'
7
+ spec.version = Laneful::VERSION
8
+ spec.authors = ['Laneful Team']
9
+ spec.email = ['support@laneful.com']
10
+
11
+ spec.summary = 'Ruby SDK for the Laneful email API'
12
+ spec.description = 'A modern Ruby client library for the Laneful email API, ' \
13
+ 'providing easy integration for email sending, webhooks, and analytics.'
14
+ spec.homepage = 'https://github.com/laneful/laneful-ruby'
15
+ spec.license = 'MIT'
16
+ spec.required_ruby_version = '>= 3.0.0'
17
+
18
+ spec.metadata['homepage_uri'] = spec.homepage
19
+ spec.metadata['source_code_uri'] = 'https://github.com/laneful/laneful-ruby'
20
+ spec.metadata['changelog_uri'] = 'https://github.com/laneful/laneful-ruby/blob/main/CHANGELOG.md'
21
+ spec.metadata['documentation_uri'] = 'https://docs.laneful.com/ruby'
22
+ spec.metadata['rubygems_mfa_required'] = 'true'
23
+
24
+ # Specify which files should be added to the gem when it is released.
25
+ spec.files = Dir.chdir(__dir__) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (File.expand_path(f) == __FILE__) ||
28
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile])
29
+ end
30
+ end
31
+ spec.bindir = 'exe'
32
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ['lib']
34
+
35
+ # Dependencies
36
+ spec.add_dependency 'httparty', '~> 0.21'
37
+ spec.add_dependency 'json', '~> 2.6'
38
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Laneful
4
+ # Main client for communicating with the Laneful email API
5
+ class Client
6
+ include HTTParty
7
+
8
+ # Default timeout in seconds
9
+ DEFAULT_TIMEOUT = 30
10
+
11
+ attr_reader :base_url, :auth_token, :timeout
12
+
13
+ def initialize(base_url, auth_token, timeout: DEFAULT_TIMEOUT)
14
+ @base_url = base_url&.strip
15
+ @auth_token = auth_token&.strip
16
+ @timeout = timeout
17
+
18
+ validate_configuration!
19
+
20
+ # Configure HTTParty
21
+ self.class.base_uri @base_url
22
+ self.class.default_timeout @timeout
23
+ self.class.headers default_headers
24
+ end
25
+
26
+ # Sends a single email
27
+ def send_email(email)
28
+ send_emails([email])
29
+ end
30
+
31
+ # Sends multiple emails
32
+ def send_emails(emails)
33
+ validate_emails!(emails)
34
+
35
+ request_data = { 'emails' => emails.map(&:to_hash) }
36
+ response = self.class.post("/#{API_VERSION}/email/send", body: request_data.to_json)
37
+
38
+ handle_response(response)
39
+ end
40
+
41
+ private
42
+
43
+ def validate_configuration!
44
+ raise ValidationException, 'Base URL cannot be empty' if base_url.nil? || base_url.empty?
45
+
46
+ raise ValidationException, 'Auth token cannot be empty' if auth_token.nil? || auth_token.empty?
47
+
48
+ # Basic URL validation
49
+ return if base_url.match?(%r{^https?://})
50
+
51
+ raise ValidationException, 'Base URL must be a valid HTTP/HTTPS URL'
52
+ end
53
+
54
+ def validate_emails!(emails)
55
+ raise ValidationException, 'Emails list cannot be empty' if emails.nil? || emails.empty?
56
+
57
+ emails.each do |email|
58
+ raise ValidationException, 'All emails must be Email instances' unless email.is_a?(Email)
59
+ end
60
+ end
61
+
62
+ def default_headers
63
+ {
64
+ 'Authorization' => "Bearer #{auth_token}",
65
+ 'Content-Type' => 'application/json',
66
+ 'Accept' => 'application/json',
67
+ 'User-Agent' => USER_AGENT
68
+ }
69
+ end
70
+
71
+ def handle_response(response)
72
+ case response.code
73
+ when 200, 201, 202
74
+ parse_success_response(response)
75
+ when 404
76
+ raise HttpException.new(
77
+ "API endpoint not found (404). Check your base URL. Requested: #{response.request.last_uri}",
78
+ response.code
79
+ )
80
+ else
81
+ handle_error_response(response)
82
+ end
83
+ rescue JSON::ParserError => e
84
+ error_message = "Failed to decode JSON response: #{e.message}. " \
85
+ "Response body: #{truncate_response_body(response.body)}. " \
86
+ "URL: #{response.request.last_uri}"
87
+ raise HttpException.new(error_message, response.code, e)
88
+ end
89
+
90
+ def parse_success_response(response)
91
+ return {} if response.body.nil? || response.body.strip.empty?
92
+
93
+ JSON.parse(response.body)
94
+ end
95
+
96
+ def handle_error_response(response)
97
+ error_data = parse_error_response(response)
98
+ error_message = error_data['error'] || 'Unknown API error'
99
+ details = error_data['details'] || ''
100
+ full_error = details.empty? ? error_message : "#{error_message} - #{details}"
101
+
102
+ raise ApiException.new(
103
+ "API request failed to #{response.request.last_uri}",
104
+ response.code,
105
+ full_error
106
+ )
107
+ end
108
+
109
+ def parse_error_response(response)
110
+ return {} if response.body.nil? || response.body.strip.empty?
111
+
112
+ JSON.parse(response.body)
113
+ rescue JSON::ParserError
114
+ { 'error' => 'Invalid JSON response', 'details' => truncate_response_body(response.body) }
115
+ end
116
+
117
+ def truncate_response_body(body, max_length: 500)
118
+ return body if body.nil? || body.length <= max_length
119
+
120
+ "#{body[0, max_length]}..."
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Laneful
4
+ # Base exception class for all Laneful SDK exceptions
5
+ class LanefulException < StandardError
6
+ def initialize(message = nil, cause = nil)
7
+ super(message)
8
+ @cause = cause
9
+ end
10
+
11
+ attr_reader :cause
12
+ end
13
+
14
+ # Exception thrown when input validation fails
15
+ class ValidationException < LanefulException
16
+ def initialize(message = nil, cause = nil)
17
+ super
18
+ end
19
+ end
20
+
21
+ # Exception thrown when the API returns an error response
22
+ class ApiException < LanefulException
23
+ attr_reader :status_code, :error_message
24
+
25
+ def initialize(message = nil, status_code = nil, error_message = nil, cause: nil)
26
+ super(message, cause)
27
+ @status_code = status_code
28
+ @error_message = error_message
29
+ end
30
+ end
31
+
32
+ # Exception thrown when HTTP communication fails
33
+ class HttpException < LanefulException
34
+ attr_reader :status_code
35
+
36
+ def initialize(message = nil, status_code = nil, cause = nil)
37
+ super(message, cause)
38
+ @status_code = status_code
39
+ end
40
+ end
41
+ end