emarsys-broadcast 0.1.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/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ test.rb
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.2"
4
+ - "1.9.3"
5
+ - "2.0.0"
6
+ - "jruby-19mode"
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Valentin Vasilyev
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,284 @@
1
+ # Emarsys::Broadcast
2
+
3
+ Ruby wrapper for Emarsys batch mailing API
4
+ ==========================================
5
+ [![Build Status](https://travis-ci.org/Valve/emarsys-broadcast-ruby.png)](https://travis-ci.org/Valve/emarsys-broadcast-ruby)
6
+ [![Code Climate](https://codeclimate.com/github/Valve/emarsys-broadcast-ruby.png)](https://codeclimate.com/github/Valve/emarsys-broadcast-ruby)
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'emarsys-broadcast'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install emarsys-broadcast
21
+
22
+ ## Usage
23
+
24
+ ### Complete sending example:
25
+
26
+ Minimal configuration is required before usage
27
+
28
+ ```ruby
29
+ # if using rails, place this configuration code into initializer
30
+ Emarsys::Broadcast::configure do |c|
31
+ c.api_user = your_api_user
32
+ c.api_password = your_api_password
33
+
34
+ c.sftp_user = your_sftp_user
35
+ c.sftp_password = your_sftp_password
36
+
37
+ c.sender_domain = 'mail.your.company.com'
38
+ end
39
+
40
+
41
+ # create a batch that you want to send
42
+ batch = Emarsys::Broadcast::Batch.new
43
+ batch.sender = 'sender_id'
44
+ batch.name = 'newsletter_2013_06_01'
45
+ batch.subject = 'June 2013 company news'
46
+ batch.body_html = '<h1>Dear 朋友!</h1>'
47
+ batch.recipients_path = '/path/to/your/csv/with/emails'
48
+
49
+ # create API client
50
+ api = Emarsys::Broadcast::API.new
51
+
52
+ # now send your batch
53
+ api.send_batch(batch)
54
+ ```
55
+
56
+ This will synchronously send the batch email to all recipients found in CSV file.
57
+
58
+ ### Moving batch properties to configuration
59
+
60
+ If you find yourself using same batch attributes over and over again, for example `recipients_path`,
61
+ you can move those values into configuration:
62
+
63
+ ```ruby
64
+ Emarsys::Broadcast::configure do |c|
65
+ c.api_user = your_api_user
66
+ c.api_password = your_api_password
67
+
68
+ c.sftp_user = your_sftp_user
69
+ c.sftp_password = your_sftp_password
70
+
71
+ c.sender = 'sender_id'
72
+ c.sender_domain = 'mail.your.company.com'
73
+ c.recipients_path = '/path/to/hyour/csv/with/emails'
74
+ end
75
+
76
+ # now you can omit these attributes when constructing a batch:
77
+ batch = Emarsys::Broadcast::Batch.new
78
+ batch.name = 'newsletter_2013_06_01'
79
+ batch.subject = 'June 2013 company news'
80
+ batch.body_html = '<h1>Dear 朋友!</h1>'
81
+
82
+ # send your batch as above, via api
83
+ ```
84
+
85
+
86
+ ### Creating batch from hash
87
+
88
+ If you like, you can construct your batch from a hash, like follows:
89
+
90
+ ```ruby
91
+ batch = Emarsys::Broadcast::Batch.new name: 'name', subject: 'subject', body_html: '<h1>html body</h1>'
92
+ ```
93
+
94
+ ### Batch name requirements
95
+
96
+ Batch name must be a valid identifier, i.e. start with a letter and contain letters, digits and underscores.
97
+ Emarsys requires every batch to have a unique name, but you don't have to maintain the uniqueness, because
98
+ this library internally appends a timestamp to each batch name before submitting it to Emarsys.
99
+
100
+ ### Batch subject requirements
101
+
102
+ Batch subject must be a string with a maximum length of 255 characters
103
+
104
+ ### Batch body html
105
+
106
+ Batch body html can be any HTML text, no restrictions
107
+
108
+ ### Batch body in plain text
109
+
110
+ It is possible to supply the body contents in plain text, this will broaden compatibility,
111
+ because some email clients don't support HTML and will download textual version instead.
112
+
113
+ ```ruby
114
+ batch = Emarsys::Broadcast::Batch.new
115
+ batch.name = 'newsletter_2013_06_01'
116
+ batch.subject = 'June 2013 company news'
117
+ batch.body_html = '<h1>Dear 朋友!</h1>'
118
+ batch.body_text = 'Dear 朋友'
119
+ ```
120
+
121
+ ### Batch validation
122
+
123
+ Emarsys::Broadcast uses ActiveModel for validating plain ruby objects, so you have all the methods for
124
+ validation you're accustomed to:
125
+
126
+ ```ruby
127
+ batch = Emarsys::Broadcast::Batch.new
128
+ batch.name = 'newsletter_2013_06_01'
129
+
130
+ batch.valid? # false
131
+ batch.errors
132
+ batch.errors.full_messages
133
+ ```
134
+
135
+ You can always validate your batch before submitting it.
136
+
137
+ Note that calling api#send_batch on an invalid batch will throw ValidationException
138
+
139
+ ```ruby
140
+ batch = get_invalid_batch
141
+ api = Emarsys::Broadcast::API.new
142
+ begin
143
+ api.send_batch batch
144
+ rescue Emarsys::Broadcast::ValidationException => ex
145
+ # get exception message
146
+ puts ex.message
147
+ # get exception errors (ActiveModel compatible)
148
+ puts ex.errors
149
+ end
150
+ ```
151
+
152
+ ### CSV file requirements
153
+
154
+ The recipients must be placed in a `UTF-8` CSV file.
155
+ The file must have at least one column with `EMAIL` header, for example:
156
+
157
+ ```csv
158
+ EMAIL
159
+ john.doe@gmail.com
160
+ sara.parker@yahoo.com
161
+ ...
162
+ ...
163
+ ```
164
+
165
+ If you use additional customization columns, add them to your CSV file:
166
+
167
+ ```csv
168
+ EMAIL FIRST_NAME
169
+ john.doe@gmail.com John
170
+ sara.parker@yahoo.com Sara
171
+ ...
172
+ ...
173
+ ```
174
+ Having additional columns allows you to customize your body_html, for example:
175
+
176
+ ```html
177
+ <h1>Hi, $$FIRST_NAME$$</h1>
178
+ ```
179
+
180
+ ### Batch sender_id requirements
181
+
182
+ Emarsys requires that API users maintain a list of possible senders, and restricts
183
+ sending emails from arbitrary sender.
184
+
185
+ To use any `sender_id` in your batch, create it first:
186
+
187
+ ```ruby
188
+ # assuming you have API configured already
189
+ api = Emarsys::Broadcast::API.new
190
+ # sender requires 3 arguments: id, name, email_address
191
+ sender = Emarsys::Broadcast::Sender.new('primary_newsletter_sender', 'My company', 'news@company.com')
192
+ api.create_sender sender
193
+ ```
194
+
195
+ Once you upload a sender, you can use its ID in any batch:
196
+
197
+ ```ruby
198
+ batch.sender_id = 'primary_newsletter_sender'
199
+ # more attributes
200
+ ```
201
+
202
+ ### Working with senders
203
+
204
+ #### Getting a full list of senders
205
+
206
+ ```ruby
207
+ api.get_senders
208
+ # returns Sender array
209
+ ```
210
+
211
+ #### Getting a single sender by email
212
+
213
+ ```ruby
214
+ api.get_sender('news@mycompany.ru')
215
+ ```
216
+
217
+ #### Find if a sender exists by email
218
+
219
+ ```ruby
220
+ api.sender_exists? 'news@mycompany.ru'
221
+ ```
222
+
223
+ ### Scheduling batches
224
+
225
+ By default a new batch is scheduled for immediate sending, but you can set the `send_time`
226
+
227
+ ```ruby
228
+ # Assuming using ActiveSupport and want to schedule a batch to be sent in 10 days
229
+ batch.send_time = Time.zone.now + 10.days
230
+ # .. more attributes
231
+
232
+ api.send_batch batch
233
+ ```
234
+
235
+ ### Compatibility
236
+
237
+ This gem is tested on
238
+ * MRI `1.9.2`, `1.9.3`, `2.0.0`
239
+ * JRuby 1.9 mode
240
+
241
+
242
+ ### Further plans
243
+
244
+ This library does not yet cover all Emarsys functionality, so the plans are to cover 100% of Emarsys features,
245
+ add async support, more scheduling options etc.
246
+
247
+ If you want to help me with this, pull requests are especially welcome :)
248
+
249
+
250
+ ## Contributing
251
+
252
+ 1. Fork it
253
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
254
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
255
+ 4. Run specs (`rspec`)
256
+ 5. Push to the branch (`git push origin my-new-feature`)
257
+ 6. Create new Pull Request
258
+
259
+
260
+ ## License
261
+
262
+
263
+ Copyright (c) 2013 Valentin Vasilyev
264
+
265
+ MIT License
266
+
267
+ Permission is hereby granted, free of charge, to any person obtaining
268
+ a copy of this software and associated documentation files (the
269
+ "Software"), to deal in the Software without restriction, including
270
+ without limitation the rights to use, copy, modify, merge, publish,
271
+ distribute, sublicense, and/or sell copies of the Software, and to
272
+ permit persons to whom the Software is furnished to do so, subject to
273
+ the following conditions:
274
+
275
+ The above copyright notice and this permission notice shall be
276
+ included in all copies or substantial portions of the Software.
277
+
278
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
279
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
280
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
281
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
282
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
283
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
284
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+
6
+ task(default: :spec)
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'emarsys/broadcast/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "emarsys-broadcast"
8
+ spec.version = Emarsys::Broadcast::VERSION
9
+ spec.authors = ["Valentin Vasilyev"]
10
+ spec.email = ["iamvalentin@gmail.com"]
11
+ spec.description = %q{Emarsys broadcast API for Ruby}
12
+ spec.summary = %q{Emarsys broadcast API for Ruby}
13
+ spec.homepage = "https://github.com/Valve/emarsys-broadcast-ruby"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "net-sftp"
22
+ spec.add_dependency "nokogiri"
23
+ spec.add_dependency "activemodel", "~> 3.0"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rspec", "~> 2.11"
28
+ spec.add_development_dependency "timecop"
29
+ spec.add_development_dependency "webmock"
30
+ end
@@ -0,0 +1,78 @@
1
+ module Emarsys
2
+ module Broadcast
3
+ class API
4
+
5
+ def initialize
6
+ @config = Emarsys::Broadcast.configuration
7
+ @sftp = SFTP.new @config
8
+ @http = HTTP.new @config
9
+ @xml_builder = XmlBuilder.new
10
+ end
11
+
12
+ def send_batch(batch)
13
+ batch = supplement_batch_from_config(batch)
14
+ validate_batch(batch)
15
+ validate_sender(batch.sender)
16
+ create_batch(batch)
17
+ upload_recipients(batch.recipients_path)
18
+ trigger_import(batch)
19
+ end
20
+
21
+ def create_batch(batch)
22
+ emarsys_sender = get_sender(batch.sender)
23
+ batch.sender_id = emarsys_sender.id
24
+ batch_xml = BatchXmlBuilder.new.build(batch)
25
+ @http.post("#{@config.api_base_path}/batches/#{batch.name}", batch_xml)
26
+ end
27
+
28
+ def upload_recipients(recipients_path)
29
+ @sftp.upload_file(recipients_path, File.basename(recipients_path))
30
+ end
31
+
32
+ def trigger_import(batch)
33
+ import_xml = XmlBuilder.new.import_xml(File.basename(batch.recipients_path))
34
+ @http.post("#{@config.api_base_path}/batches/#{batch.name}/import", import_xml)
35
+ end
36
+
37
+ def get_senders
38
+ response = @http.get("#{@config.api_base_path}/senders")
39
+ Nokogiri::XML(response).xpath('//sender').map do |node|
40
+ Sender.new(node.attr('id'), node.xpath('name').text, node.xpath('address').text)
41
+ end
42
+ end
43
+
44
+ def get_sender(email)
45
+ get_senders.find{|s| s.address == email}
46
+ end
47
+
48
+ def create_sender(sender)
49
+ sender_xml = @xml_builder.sender_xml(sender)
50
+ @http.put("#{@config.api_base_path}/senders/#{sender.id}", sender_xml)
51
+ end
52
+
53
+ def sender_exists?(email)
54
+ get_senders.any?{|s|s.address == email}
55
+ end
56
+
57
+ private
58
+
59
+ def supplement_batch_from_config(batch)
60
+ batch.recipients_path ||= @config.recipients_path
61
+ batch.send_time ||= Time.now
62
+ batch.sender ||= @config.sender
63
+ batch.sender_domain ||= @config.sender_domain
64
+ batch
65
+ end
66
+
67
+ def validate_batch(batch)
68
+ raise ValidationError.new('Batch is invalid', batch.errors.full_messages) unless batch.valid?
69
+ end
70
+
71
+ def validate_sender(email)
72
+ msg = 'This email is not registered with Emarsys as a sender, register it with `create_sender` api call'
73
+ raise ValidationError, msg, [msg] unless sender_exists? email
74
+ end
75
+ end
76
+ class ApiError < StandardError; end
77
+ end
78
+ end
@@ -0,0 +1,25 @@
1
+ require 'uri'
2
+ module Emarsys
3
+ module Broadcast
4
+ class Batch
5
+ include ActiveModel::Validations
6
+ attr_accessor \
7
+ :name,
8
+ :subject,
9
+ :body_html,
10
+ :body_text,
11
+ :recipients_path,
12
+ :send_time,
13
+ :sender,
14
+ :sender_domain,
15
+ :sender_id
16
+
17
+ validates :name, :subject, :body_html, :recipients_path, :sender, :sender_domain, presence: true
18
+ validates :name, format: {with: /^[^\d\W]\w*\z/i, message: 'must start with a letter and contain only letters, numbers and underscores'}
19
+ validates :subject, length: {maximum: 255}
20
+ validates :sender, format: {with: /@/, message: 'is not a valid email'}
21
+ validates :sender_domain, format: {with: URI::REL_URI, message: 'is not valid'}
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ require 'nokogiri'
2
+ module Emarsys
3
+ module Broadcast
4
+ class BatchXmlBuilder
5
+
6
+ def build(batch)
7
+ raise ArgumentError, 'batch is required' unless batch
8
+ builder = Nokogiri::XML::Builder.new do |xml|
9
+ xml.batch {
10
+ xml.name batch.name
11
+ xml.runDate format_time(batch.send_time)
12
+ xml.properties {
13
+ xml.property(key: :Sender){xml.text batch.sender_id}
14
+ xml.property(key: :Language){xml.text 'en'}
15
+ xml.property(key: :Encoding){xml.text 'UTF-8'}
16
+ xml.property(key: :Domain){xml.text batch.sender_domain}
17
+ }
18
+ xml.subject batch.subject
19
+ xml.html batch.body_html
20
+ xml.text batch.body_text
21
+ }
22
+ end
23
+ builder.to_xml
24
+ end
25
+
26
+
27
+ private
28
+
29
+ def format_time(time)
30
+ time.strftime("%Y-%m-%dT%H:%M:%S%z")
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,43 @@
1
+ module Emarsys
2
+ module Broadcast
3
+ class ConfigurationError < StandardError; end
4
+
5
+
6
+ class Configuration
7
+ attr_accessor \
8
+ :sftp_host,
9
+ :sftp_user,
10
+ :sftp_password,
11
+ :sftp_port,
12
+ :api_host,
13
+ :api_base_path,
14
+ :api_user,
15
+ :api_password,
16
+ :api_port,
17
+ :api_timeout,
18
+ :sender,
19
+ :sender_domain,
20
+ :recipients_path
21
+
22
+
23
+ def initialize
24
+ @sftp_host = 'e3.emarsys.net'
25
+ @sftp_port = 22
26
+ @api_host = 'e3.emarsys.net'
27
+ @api_base_path = '/bmapi/v2'
28
+ @api_port = 80
29
+ @api_timeout = 600 #10 minutes
30
+ end
31
+ end
32
+
33
+ class << self
34
+ attr_accessor :configuration
35
+ end
36
+
37
+ def self.configure
38
+ self.configuration ||= Configuration.new
39
+ yield configuration
40
+ configuration
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,11 @@
1
+ module Emarsys
2
+ module Broadcast
3
+ module Email
4
+ REGEX = /\A(|(([A-Za-z0-9]+_+)|([A-Za-z0-9]+\-+)|([A-Za-z0-9]+\.+)|([A-Za-z0-9]+\++))*[A-Za-z0-9]+@((\w+\-+)|(\w+\.))*\w{1,63}\.[a-zA-Z]{2,6})\z/i
5
+
6
+ def self.validate(email)
7
+ email =~ REGEX
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,70 @@
1
+ require 'net/http'
2
+ module Emarsys
3
+ module Broadcast
4
+ class HTTP
5
+ include Validation
6
+
7
+ def initialize(config)
8
+ validate_config config
9
+ @config = config
10
+ end
11
+
12
+ def post(path, xml)
13
+ request(path, xml, :post)
14
+ end
15
+
16
+ def put(path, xml)
17
+ request(path, xml, :put)
18
+ end
19
+
20
+ def get(path)
21
+ request(path, nil, :get)
22
+ end
23
+
24
+
25
+ private
26
+
27
+ def request(path, data, method)
28
+ https = Net::HTTP.new(@config.api_host, Net::HTTP.https_default_port)
29
+ https.read_timeout = @config.api_timeout
30
+ https.use_ssl = true
31
+ https.verify_mode = OpenSSL::SSL::VERIFY_NONE
32
+
33
+ https.start do |http|
34
+ case method.downcase.to_sym
35
+ when :post
36
+ req = Net::HTTP::Post.new(path)
37
+ when :put
38
+ req = Net::HTTP::Put.new(path)
39
+ when :get
40
+ req = Net::HTTP::Get.new(path)
41
+ end
42
+ req.basic_auth(@config.api_user, @config.api_password)
43
+ req.body = data
44
+ req.content_type = "application/xml"
45
+
46
+ res = http.request(req)
47
+
48
+ case res
49
+ when Net::HTTPSuccess
50
+ puts "OK"
51
+ return res.body
52
+ else
53
+ puts res.body
54
+ res.error!
55
+ end
56
+ end
57
+ end
58
+
59
+ def validate_config(config)
60
+ raise ConfigurationError, 'configuration is nil, did you forget to configure the gem?' unless config
61
+ raise ConfigurationError, 'api_host must be configured' unless string_present? config.api_host
62
+ raise ConfigurationError, 'api_user must be configured' unless string_present? config.api_user
63
+ raise ConfigurationError, 'api_password must be configured' unless string_present? config.api_password
64
+ unless within_range? config.api_port, 1..65535
65
+ raise ConfigurationError, 'api_port must be integer between 1 and 65535'
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,11 @@
1
+ module Emarsys
2
+ module Broadcast
3
+ class Sender
4
+ attr_reader :id, :name, :address
5
+
6
+ def initialize(id, name, address)
7
+ @id, @name, @address = id, name, address
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ require 'net/sftp'
2
+ module Emarsys
3
+ module Broadcast
4
+ class SFTP
5
+ include Validation
6
+
7
+ def initialize(config)
8
+ validate_config config
9
+ @config = config
10
+ end
11
+
12
+ def upload_file(local_path, remote_path)
13
+ Net::SFTP.start(@config.sftp_host, @config.sftp_user, password: @config.sftp_password) do |sftp|
14
+ sftp.upload!(local_path, remote_path)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def validate_config(config)
21
+ raise ConfigurationError, 'configuration is nil, did you forget to configure the gem?' unless config
22
+ raise ConfigurationError, 'sftp_host must be configured' unless string_present? config.sftp_host
23
+ raise ConfigurationError, 'sftp_user must be configured' unless string_present? config.sftp_user
24
+ raise ConfigurationError, 'sftp_password must be configured' unless string_present? config.sftp_password
25
+ unless within_range? config.sftp_port, 1..65535
26
+ raise ConfigurationError, 'sftp_port must be integer between 1 and 65535'
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end