amply-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4277fb080efe8030459c1e5c6f837738b224e368ace142d33875a41926cf8f7e
4
+ data.tar.gz: fbd25ca786496e8dba524ea98cc4c5bce9c650200201a0f1a1286da0d6e18f89
5
+ SHA512:
6
+ metadata.gz: 76066fdb26b96a85bb8628f29585e855113459ffaff974262f0ffce09eedf2fb20e637c31661e50d414b7e537e6e64bc2acba2cfd11fd11efd4eda3502b00fa6
7
+ data.tar.gz: 07b11ae389c464c513413734743f53fd912e8d9a0469e28629b181a0d2d65cdc5d36599ce776115242cc93cd50fb249dbacf5891b48b1d7b389e9297fd7e8e22
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.swp
2
+ .DS_Store
3
+ *.log
4
+ .idea/
5
+ *.gem
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (C) 2020, Send Amply Inc. <support@sendamply.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,107 @@
1
+ # Amply
2
+
3
+ This is the Amply Ruby SDK that integrates with the [v1 API](https://docs.sendamply.com/docs/api/docs/Introduction.md).
4
+
5
+ __Table of Contents__
6
+
7
+ - [Install](#install)
8
+ - [Quick Start](#quick-start)
9
+ - [Classes](#classes)
10
+ - [Email](#Email)
11
+
12
+ ## Install
13
+
14
+ ### Prerequisites
15
+ - Ruby 2.4+
16
+ - Amply account, [sign up here.](https://sendamply.com/plans)
17
+
18
+ ### Access Token
19
+
20
+ Obtain your access token from the [Amply UI.](https://sendamply.com/home/settings/access_tokens)
21
+
22
+ ### Install Package
23
+ ```
24
+ gem install amply-ruby
25
+ ```
26
+
27
+ ### Domain Verification
28
+ Add domains you want to send `from` via the [Verified Domains](https://sendamply.com/home/settings/verified_domains) tab on your dashboard.
29
+
30
+ Any emails you attempt to send from an unverified domain will be rejected. Once verified, Amply immediately starts warming up your domain and IP reputation. This warmup process will take approximately one week before maximal deliverability has been reached.
31
+
32
+ ## Quick Start
33
+ The following is the minimum needed code to send a simple email. Use this example, and modify the `to` and `from` variables:
34
+
35
+ ```ruby
36
+ require 'amply'
37
+
38
+ Amply.set_access_token(ENV['AMPLY_ACCESS_TOKEN'])
39
+
40
+ begin
41
+ Amply::Email.create(to: 'test@example.com',
42
+ from: 'test@verifieddomain.com',
43
+ subject: 'My first Amply email!',
44
+ text: 'This is easy',
45
+ html: '<strong>and fun :)</strong>')
46
+
47
+ rescue Amply::Exceptions::APIException => e
48
+ # Most likely invalid access token
49
+ puts e.status
50
+ puts e.text
51
+ rescue Amply::Exceptions::ValidationException => e
52
+ puts e.message
53
+ puts e.errors
54
+ end
55
+ ```
56
+
57
+ Once you execute this code, you should have an email in the inbox of the recipient. You can check the status of your email in the UI from the [Search](https://sendamply.com/home/analytics/searches/basic/new), [SQL](https://sendamply.com/home/analytics/searches/sql/new), or [Users](https://sendamply.com/home/analytics/users) page.
58
+
59
+ ## Methods
60
+
61
+ ### email
62
+
63
+ Parameter(s) | Description
64
+ :---------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
65
+ to, cc, bcc | Email address of the recipient(s). This may be a string `Test <test@example.com>`, a hash `{ name: 'Test', email: 'test@example.com' }`, or an array of strings and hashes.
66
+ personalizations | For fine tuned access, you may override the to, cc, and bcc keys and use advanced personalizations. See the API guide [here](https://docs.sendamply.com/docs/api/Mail-Send.v1.yaml/paths/~1email/post).
67
+ from | Email address of the sender. This may be formatted as a string or hash. An array of senders is not allowed.
68
+ subject | Subject of the message.
69
+ html | HTML portion of the message.
70
+ text | Text portion of the message.
71
+ content | An array of hashes containing the following keys: `type` (required), `value` (required).
72
+ template | The template to use. This may be a string (the UUID of the template), an array of UUID's (useful for A/B/... testing where one is randomly selected), or a hash of the format `{ template1Uuid: 0.25, template2Uuid: 0.75 }` (useful for weighted A/B/... testing).
73
+ dynamic_template_data | The dynamic data to be replaced in your template. This is an hash of the format `{ variable1: 'replacement1', ... }`. Variables should be defined in your template body as `${variable1}`.
74
+ reply_to |Email address of who should receive replies. This may be a string or a hash with `name` and `email` keys.
75
+ headers | A hash where the header name is the key and header value is the value.
76
+ ip_or_pool_uuid | The UUID of the IP address or IP pool you want to send from. Default is your Global pool.
77
+ unsubscribe_group_uuid | The UUID of the unsubscribe group you want to associate with this email.
78
+ attachments[][content] | A base64 encoded string of your attachment's content.
79
+ attachments[][type] | The MIME type of your attachment.
80
+ attachments[][filename] | The filename of your attachment.
81
+ attachments[][disposition] | The disposition of your attachment (`inline` or `attachment`).
82
+ attachments[][content_id] | The content ID of your attachment.
83
+ clicktracking | Enable or disable clicktracking.
84
+ categories | An array of email categories you can associate with your message.
85
+ substitutions | A hash of the format `{ sub_from: 'sub_to', ...}` of substitutions.
86
+
87
+ __Example__
88
+
89
+ ```ruby
90
+ Amply::Email.create(to: 'example@test.com',
91
+ from: 'From <example@verifieddomain.com>',
92
+ text: 'Text part',
93
+ html: 'HTML part',
94
+ personalizations: [{ to: [{ name: 'Override To', email: 'test@example.com' }] }],
95
+ content: [{ type: 'text/testing', value: 'some custom content type' }],
96
+ subject: 'A new email!',
97
+ reply_to: 'Reply To <test@example.com>',
98
+ template: 'faecb75b-371e-4062-89d5-372b8ff0effd',
99
+ dynamic_template_data: { name: 'Jimmy' },
100
+ unsubscribe_group_uuid: '5ac48b43-6e7e-4c51-817d-f81ea0a09816',
101
+ ip_or_pool_uuid: '2e378fc9-3e23-4853-bccb-2990fda83ca9',
102
+ attachments: [{'content': 'dGVzdA==', 'filename': 'test.txt', 'type': 'text/plain', 'disposition': 'inline'}],
103
+ headers: {'X-Testing': 'Test'},
104
+ categories: ['Test'],
105
+ clicktracking: true,
106
+ substitutions: { sub1: 'replacement1' })
107
+ ```
@@ -0,0 +1,21 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'amply/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'amply-ruby'
7
+ spec.version = Amply::VERSION
8
+ spec.email = 'support@sendamply.com'
9
+ spec.authors = ['Amply']
10
+ spec.summary = 'Amply Gem'
11
+ spec.description = 'Amply Gem to Interact with Amply\'s API in native Ruby'
12
+ spec.homepage = 'https://github.com/sendamply/amply-ruby'
13
+
14
+ spec.required_ruby_version = '>= 2.4'
15
+
16
+ spec.license = 'MIT'
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(/^bin/) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(/^(test|spec|features)/)
20
+ spec.require_paths = ['lib']
21
+ end
@@ -0,0 +1,55 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ module Amply
5
+ class Client
6
+ DEFAULT_HEADERS = {
7
+ Accept: 'application/json',
8
+ 'Content-Type': 'application/json'
9
+ }
10
+
11
+ class << self
12
+ @@access_token = ''
13
+ @@url = 'https://sendamply.com/api/v1'
14
+
15
+ def set_access_token(token)
16
+ @@access_token = token
17
+ end
18
+
19
+ def post(path, body, options = {})
20
+ uri = URI("#{@@url}#{path}")
21
+ headers = [
22
+ DEFAULT_HEADERS,
23
+ options[:headers] || {},
24
+ auth_header
25
+ ].inject(&:merge)
26
+
27
+
28
+ resp = Net::HTTP.post(uri, body.to_json, headers)
29
+ parse_response(resp)
30
+ end
31
+
32
+ def parse_response(resp)
33
+ code = resp.code.to_i
34
+
35
+ if [301, 302].include?(code)
36
+ return resp['location']
37
+ elsif [401, 403].include?(code)
38
+ raise Exceptions::APIException, resp
39
+ elsif code == 404
40
+ raise Exceptions::ResourcNotFoundException, resp
41
+ elsif code == 422
42
+ raise Exceptions::ValidationException, resp
43
+ elsif code < 200 || code >= 300
44
+ raise Exceptions::APIException, resp
45
+ end
46
+
47
+ JSON.parse(resp.body, symbolize_names: true)
48
+ end
49
+
50
+ def auth_header
51
+ { Authorization: "Bearer #{@@access_token}" }
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,12 @@
1
+ require_relative './helpers/email'
2
+
3
+ module Amply
4
+ class Email
5
+ class << self
6
+ def create(data)
7
+ parsed_data = Helpers::Email.new(data).parsed_data
8
+ Client.post('/email', parsed_data)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ module Amply
2
+ module Exceptions
3
+ class APIException < StandardError
4
+ attr_reader :status, :text
5
+
6
+ def initialize(response)
7
+ @status = response.code.to_i
8
+ @text = response.message
9
+
10
+ super
11
+ end
12
+
13
+ def message
14
+ 'An error occurred while making an API request'
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ require 'json'
2
+
3
+ module Amply
4
+ module Exceptions
5
+ class ResourceNotFoundException < StandardError
6
+ attr_reader :errors
7
+
8
+ def initialize(response)
9
+ @errors = JSON.parse(response.body, symbolize_names: true)[:errors]
10
+
11
+ super
12
+ end
13
+
14
+ def message
15
+ 'The resource was not found while making an API request'
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ require 'json'
2
+
3
+ module Amply
4
+ module Exceptions
5
+ class ValidationException < StandardError
6
+ attr_reader :errors
7
+
8
+ def initialize(response)
9
+ @errors = JSON.parse(response.body, symbolize_names: true)[:errors]
10
+
11
+ super
12
+ end
13
+
14
+ def message
15
+ 'A validation error occurred while making an API request'
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ require_relative './exceptions/api_exception'
2
+ require_relative './exceptions/validation_exception'
3
+ require_relative './exceptions/resource_not_found_exception'
@@ -0,0 +1,247 @@
1
+ require 'json'
2
+
3
+ require_relative './email_address'
4
+
5
+ module Amply
6
+ module Helpers
7
+ class Email
8
+ def initialize(data)
9
+ @data = data
10
+ @request_data = {}
11
+ end
12
+
13
+ def parsed_data
14
+ unless @data.is_a?(Hash)
15
+ raise 'Expecting hash for email data'
16
+ end
17
+
18
+ data_sym = JSON.parse(JSON.dump(@data), symbolize_names: true)
19
+
20
+ set_from(data_sym[:from])
21
+ set_subject(data_sym[:subject])
22
+ set_text(data_sym[:text])
23
+ set_html(data_sym[:html])
24
+ set_content(data_sym[:content])
25
+ set_reply_to(data_sym[:reply_to])
26
+ set_template(data_sym[:template])
27
+ set_dynamic_template_data(data_sym[:dynamic_template_data])
28
+ set_unsubscribe_group_uuid(data_sym[:unsubscribe_group_uuid])
29
+ set_ip_or_pool_uuid(data_sym[:ip_or_pool_uuid])
30
+ set_attachments(data_sym[:attachments])
31
+ set_headers(data_sym[:headers])
32
+ set_categories(data_sym[:categories])
33
+ set_clicktracking(data_sym[:clicktracking])
34
+ set_substitutions(data_sym[:substitutions])
35
+
36
+ if data_sym[:personalizations].nil?
37
+ set_personalizations_from_to(data_sym[:to], data_sym[:cc], data_sym[:bcc])
38
+ else
39
+ set_personalizations(data_sym[:personalizations])
40
+ end
41
+
42
+ @request_data
43
+ end
44
+
45
+ private
46
+
47
+ def set_from(from)
48
+ return if from.nil?
49
+ @request_data[:from] = format_emails(from)[0]
50
+ end
51
+
52
+ def set_subject(subject)
53
+ unless subject.is_a?(String)
54
+ raise 'String expected for `subject`'
55
+ end
56
+
57
+ @request_data[:subject] = subject
58
+ end
59
+
60
+ def set_text(text)
61
+ return if text.nil?
62
+
63
+ @request_data[:content] ||= []
64
+ @request_data[:content].push(type: 'text/plain', value: text)
65
+ end
66
+
67
+ def set_html(html)
68
+ return if html.nil?
69
+
70
+ @request_data[:content] ||= []
71
+ @request_data[:content].push(type: 'text/html', value: html)
72
+ end
73
+
74
+ def set_content(content)
75
+ return if content.nil?
76
+
77
+ unless content.is_a?(Array)
78
+ raise 'Array expected for `content`'
79
+ end
80
+
81
+ @request_data[:content] ||= []
82
+
83
+ content.each_with_index do |part, i|
84
+ unless part.is_a?(Hash)
85
+ raise "Hash expected for `content[#{i}]`"
86
+ end
87
+
88
+ type = part[:type] || part['type']
89
+ value = part[:value] || part['value']
90
+
91
+ if type.nil?
92
+ raise "`type` must be defined for `content[#{i}][type]`"
93
+ end
94
+
95
+ if value.nil?
96
+ raise "`value` must be defined for `content[#{i}][type]`"
97
+ end
98
+
99
+ @request_data[:content].push(type: type, value: value)
100
+ end
101
+ end
102
+
103
+ def set_reply_to(reply_to)
104
+ return if reply_to.nil?
105
+ @request_data[:reply_to] = format_emails(reply_to)[0]
106
+ end
107
+
108
+ def set_template(template)
109
+ return if template.nil?
110
+ @request_data[:template] = template
111
+ end
112
+
113
+ def set_dynamic_template_data(dynamic_template_data)
114
+ return if dynamic_template_data.nil?
115
+
116
+ unless dynamic_template_data.is_a?(Hash)
117
+ raise 'Hash expected for `dynamic_template_data`'
118
+ end
119
+
120
+ @request_data[:substitutions] ||= {}
121
+
122
+ dynamic_template_data.each do |sub_from, sub_to|
123
+ @request_data[:substitutions]["${#{sub_from}}"] = sub_to.to_s
124
+ end
125
+ end
126
+
127
+ def set_unsubscribe_group_uuid(unsubscribe_group_uuid)
128
+ return if unsubscribe_group_uuid.nil?
129
+ @request_data[:unsubscribe_group_uuid] = unsubscribe_group_uuid
130
+ end
131
+
132
+ def set_ip_or_pool_uuid(ip_or_pool_uuid)
133
+ return if ip_or_pool_uuid.nil?
134
+ @request_data[:ip_or_pool_uuid] = ip_or_pool_uuid
135
+ end
136
+
137
+ def set_attachments(attachments)
138
+ return if attachments.nil?
139
+
140
+ unless attachments.is_a?(Array)
141
+ raise 'Array expected for `attachments`'
142
+ end
143
+
144
+ @request_data[:attachments] ||= []
145
+
146
+ attachments.each_with_index do |attachment, i|
147
+ unless attachment.is_a?(Hash)
148
+ raise "Hash expected for `attachments[#{i}]`"
149
+ end
150
+
151
+ content = attachment[:content] || attachment['content']
152
+ filename = attachment[:filename] || attachment['filename']
153
+ type = attachment[:type] || attachment['type']
154
+ disposition = attachment[:disposition] || attachment['disposition']
155
+
156
+ if content.nil?
157
+ raise "`content` must be defined for `attachments[#{i}][content]`"
158
+ end
159
+
160
+ if filename.nil?
161
+ raise "`filename` must be defined for `attachments[#{i}][filename]`"
162
+ end
163
+
164
+ data = { content: content, filename: filename }
165
+ data.merge!(type: type) unless type.nil?
166
+ data.merge!(disposition: disposition) unless disposition.nil?
167
+
168
+ @request_data[:attachments].push(data)
169
+ end
170
+ end
171
+
172
+ def set_headers(headers)
173
+ return if headers.nil?
174
+
175
+ unless headers.is_a?(Hash)
176
+ raise 'Hash expected for `headers`'
177
+ end
178
+
179
+ @request_data[:headers] ||= {}
180
+
181
+ headers.each do |name, value|
182
+ @request_data[:headers][name] = value.to_s
183
+ end
184
+ end
185
+
186
+ def set_categories(categories)
187
+ return if categories.nil?
188
+
189
+ unless categories.is_a?(Array)
190
+ raise 'Array expected for `categories`'
191
+ end
192
+
193
+ @request_data[:analytics] ||= {}
194
+ @request_data[:analytics][:categories] = categories.map { |category| category.to_s }
195
+ end
196
+
197
+ def set_clicktracking(clicktracking)
198
+ return if clicktracking.nil?
199
+
200
+ unless [TrueClass, FalseClass].include?(clicktracking.class)
201
+ raise 'Expecting TrueClass or FalseClass for `clicktracking`'
202
+ end
203
+
204
+ @request_data[:analytics] ||= {}
205
+ @request_data[:analytics][:clicktracking] = clicktracking
206
+ end
207
+
208
+ def set_substitutions(substitutions)
209
+ return if substitutions.nil?
210
+
211
+ unless substitutions.is_a?(Hash)
212
+ raise 'Hash expected for `substitutions`'
213
+ end
214
+
215
+ @request_data[:substitutions] ||= {}
216
+
217
+ substitutions.each do |sub_from, sub_to|
218
+ @request_data[:substitutions][sub_from] = sub_to.to_s
219
+ end
220
+ end
221
+
222
+ def set_personalizations_from_to(to, cc, bcc)
223
+ @request_data[:personalizations] = [{}]
224
+
225
+ if to.nil? && cc.nil? && bcc.nil?
226
+ raise 'Provide at least one of `to`, `cc` or `bcc`'
227
+ end
228
+
229
+ @request_data[:personalizations][0][:to] = format_emails(to) unless to.nil?
230
+ @request_data[:personalizations][0][:cc] = format_emails(cc) unless cc.nil?
231
+ @request_data[:personalizations][0][:bcc] = format_emails(bcc) unless bcc.nil?
232
+ end
233
+
234
+ def set_personalizations(personalizations)
235
+ @request_data[:personalizations] = personalizations
236
+ end
237
+
238
+ def format_emails(emails)
239
+ if emails.is_a?(Array)
240
+ return emails.map { |email| EmailAddress.new(email).to_json }
241
+ end
242
+
243
+ [EmailAddress.new(emails).to_json]
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,84 @@
1
+ module Amply
2
+ module Helpers
3
+ class EmailAddress
4
+ class << self
5
+ def create(data)
6
+ if data.is_a?(Array)
7
+ return data.reject { |el| el.nil? || el == '' }
8
+ .map { |el| self.class.create(el) }
9
+ end
10
+
11
+ if data.is_a?(EmailAddress)
12
+ return data
13
+ end
14
+
15
+ self.class.create(data)
16
+ end
17
+ end
18
+
19
+ def initialize(data)
20
+ if data.is_a?(String)
21
+ data = from_string(data)
22
+ end
23
+
24
+ unless data.is_a?(Hash)
25
+ raise 'Expecting hash or string for email address data'
26
+ end
27
+
28
+ name = data[:name] || data['name']
29
+ email = data[:email] || data['email']
30
+
31
+ set_name(name)
32
+ set_email(email)
33
+ end
34
+
35
+ def to_json
36
+ json = { email: @email }
37
+
38
+ unless @name.nil?
39
+ json[:name] = @name
40
+ end
41
+
42
+ json
43
+ end
44
+
45
+ private
46
+
47
+ def set_name(name)
48
+ return if name.nil?
49
+
50
+ unless name.is_a?(String)
51
+ raise 'String expected for `name`'
52
+ end
53
+
54
+ @name = name
55
+ end
56
+
57
+ def set_email(email)
58
+ if email.nil?
59
+ raise 'Must provide `email`'
60
+ end
61
+
62
+ unless email.is_a?(String)
63
+ raise 'String expected for `email`'
64
+ end
65
+
66
+ @email = email
67
+ end
68
+
69
+ def from_string(data)
70
+ if data.index('<').nil?
71
+ return { name: nil, email: data }
72
+ end
73
+
74
+ name, email = data.split('<')
75
+
76
+ name.strip!
77
+ email.gsub!('>', '')
78
+ email.strip!
79
+
80
+ { name: name, email: email }
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,3 @@
1
+ module Amply
2
+ VERSION = '0.0.1'
3
+ end
data/lib/amply.rb ADDED
@@ -0,0 +1,12 @@
1
+ require_relative 'amply/version'
2
+ require_relative 'amply/exceptions'
3
+ require_relative 'amply/client'
4
+ require_relative 'amply/email'
5
+
6
+ module Amply
7
+ class << self
8
+ def set_access_token(token)
9
+ Client.set_access_token(token)
10
+ end
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: amply-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Amply
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-12-28 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Amply Gem to Interact with Amply's API in native Ruby
14
+ email: support@sendamply.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - ".gitignore"
20
+ - LICENSE
21
+ - README.md
22
+ - amply-ruby.gemspec
23
+ - lib/amply.rb
24
+ - lib/amply/client.rb
25
+ - lib/amply/email.rb
26
+ - lib/amply/exceptions.rb
27
+ - lib/amply/exceptions/api_exception.rb
28
+ - lib/amply/exceptions/resource_not_found_exception.rb
29
+ - lib/amply/exceptions/validation_exception.rb
30
+ - lib/amply/helpers/email.rb
31
+ - lib/amply/helpers/email_address.rb
32
+ - lib/amply/version.rb
33
+ homepage: https://github.com/sendamply/amply-ruby
34
+ licenses:
35
+ - MIT
36
+ metadata: {}
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '2.4'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubyforge_project:
53
+ rubygems_version: 2.7.6
54
+ signing_key:
55
+ specification_version: 4
56
+ summary: Amply Gem
57
+ test_files: []