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.
- checksums.yaml +7 -0
- data/.rubocop.yml +31 -0
- data/LICENSE +21 -0
- data/Makefile +49 -0
- data/README.md +456 -0
- data/Rakefile +8 -0
- data/examples/Gemfile +5 -0
- data/examples/README.md +53 -0
- data/examples/simple_example.rb +121 -0
- data/laneful-ruby.gemspec +38 -0
- data/lib/laneful/client.rb +123 -0
- data/lib/laneful/exceptions.rb +41 -0
- data/lib/laneful/models.rb +442 -0
- data/lib/laneful/version.rb +5 -0
- data/lib/laneful/webhooks.rb +38 -0
- data/lib/laneful.rb +27 -0
- data/scripts/publish.sh +169 -0
- metadata +93 -0
@@ -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
|