mailgun-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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/Gemfile +4 -0
- data/LICENSE +191 -0
- data/MessageBuilder.md +85 -0
- data/Messages.md +77 -0
- data/OptInHandler.md +103 -0
- data/README.md +150 -0
- data/Rakefile +33 -0
- data/Snippets.md +506 -0
- data/lib/mailgun.rb +227 -0
- data/lib/mailgun/exceptions/exceptions.rb +27 -0
- data/lib/mailgun/lists/opt_in_handler.rb +46 -0
- data/lib/mailgun/messages/batch_message.rb +139 -0
- data/lib/mailgun/messages/message_builder.rb +321 -0
- data/lib/mailgun/version.rb +5 -0
- data/mailgun.gemspec +42 -0
- data/spec/integration/mailgun_spec.rb +598 -0
- data/spec/integration/messages/sample_data/mime.txt +38 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/unit/connection/test_client.rb +143 -0
- data/spec/unit/lists/opt_in_handler_spec.rb +22 -0
- data/spec/unit/mailgun_spec.rb +131 -0
- data/spec/unit/messages/batch_message_spec.rb +130 -0
- data/spec/unit/messages/message_builder_spec.rb +359 -0
- data/spec/unit/messages/sample_data/mailgun_icon.png +0 -0
- data/spec/unit/messages/sample_data/mime.txt +38 -0
- data/spec/unit/messages/sample_data/rackspace_logo.jpg +0 -0
- metadata +171 -0
data/lib/mailgun.rb
ADDED
@@ -0,0 +1,227 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'rest_client'
|
3
|
+
require 'yaml'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
require "mailgun/version"
|
7
|
+
require "mailgun/lists/opt_in_handler"
|
8
|
+
require "mailgun/messages/batch_message"
|
9
|
+
require "mailgun/messages/message_builder"
|
10
|
+
require "mailgun/exceptions/exceptions"
|
11
|
+
|
12
|
+
module Mailgun
|
13
|
+
|
14
|
+
# A Mailgun::Client object is used to communicate with the Mailgun API. It is a
|
15
|
+
# wrapper around RestClient so you don't have to worry about the HTTP aspect
|
16
|
+
# of communicating with our API.
|
17
|
+
#
|
18
|
+
# See the Github documentation for full examples.
|
19
|
+
|
20
|
+
class Client
|
21
|
+
|
22
|
+
def initialize(api_key,
|
23
|
+
api_host="api.mailgun.net",
|
24
|
+
api_version="v2",
|
25
|
+
ssl=true)
|
26
|
+
|
27
|
+
endpoint = endpoint_generator(api_host, api_version, ssl)
|
28
|
+
@http_client = RestClient::Resource.new(endpoint,
|
29
|
+
:user => "api",
|
30
|
+
:password => api_key,
|
31
|
+
:user_agent => "mailgun-sdk-ruby/#{Mailgun::VERSION}")
|
32
|
+
end
|
33
|
+
|
34
|
+
# Simple Message Sending
|
35
|
+
#
|
36
|
+
# @param [String] working_domain This is the domain you wish to send from.
|
37
|
+
# @param [Hash] data This should be a standard Hash or Multimap
|
38
|
+
# containing required parameters for the requested resource.
|
39
|
+
# @return [Mailgun::Response] A Mailgun::Response object.
|
40
|
+
|
41
|
+
def send_message(working_domain, data)
|
42
|
+
case data
|
43
|
+
when Hash, Multimap
|
44
|
+
if data.has_key?(:message)
|
45
|
+
if data[:message].is_a?(String)
|
46
|
+
data[:message] = convert_string_to_file(data[:message])
|
47
|
+
end
|
48
|
+
post("#{working_domain}/messages.mime", data)
|
49
|
+
else
|
50
|
+
post("#{working_domain}/messages", data)
|
51
|
+
end
|
52
|
+
when MessageBuilder
|
53
|
+
post("#{working_domain}/messages", data.message)
|
54
|
+
else
|
55
|
+
raise ParameterError.new("Unknown data type for data parameter.", data)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Generic Mailgun POST Handler
|
60
|
+
#
|
61
|
+
# @param [String] resource_path This is the API resource you wish to interact
|
62
|
+
# with. Be sure to include your domain, where necessary.
|
63
|
+
# @param [Hash] data This should be a standard Hash or Multimap
|
64
|
+
# containing required parameters for the requested resource.
|
65
|
+
# @return [Mailgun::Response] A Mailgun::Response object.
|
66
|
+
|
67
|
+
def post(resource_path, data)
|
68
|
+
begin
|
69
|
+
response = @http_client[resource_path].post(data)
|
70
|
+
Response.new(response)
|
71
|
+
rescue Exception => e
|
72
|
+
raise CommunicationError.new(e), e.response
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Generic Mailgun GET Handler
|
77
|
+
#
|
78
|
+
# @param [String] resource_path This is the API resource you wish to interact
|
79
|
+
# with. Be sure to include your domain, where necessary.
|
80
|
+
# @param [Hash] query_string This should be a standard Hash or Multimap
|
81
|
+
# containing required parameters for the requested resource.
|
82
|
+
# @return [Mailgun::Response] A Mailgun::Response object.
|
83
|
+
|
84
|
+
def get(resource_path, params=nil)
|
85
|
+
begin
|
86
|
+
if params
|
87
|
+
response = @http_client[resource_path].get(:params => params)
|
88
|
+
else
|
89
|
+
response = @http_client[resource_path].get()
|
90
|
+
end
|
91
|
+
Response.new(response)
|
92
|
+
rescue Exception => e
|
93
|
+
raise CommunicationError.new(e), e.response
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Generic Mailgun PUT Handler
|
98
|
+
#
|
99
|
+
# @param [String] resource_path This is the API resource you wish to interact
|
100
|
+
# with. Be sure to include your domain, where necessary.
|
101
|
+
# @param [Hash] data This should be a standard Hash or Multimap
|
102
|
+
# containing required parameters for the requested resource.
|
103
|
+
# @return [Mailgun::Response] A Mailgun::Response object.
|
104
|
+
|
105
|
+
def put(resource_path, data)
|
106
|
+
begin
|
107
|
+
response = @http_client[resource_path].put(data)
|
108
|
+
Response.new(response)
|
109
|
+
rescue Exception => e
|
110
|
+
raise CommunicationError.new(e), e.response
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Generic Mailgun DELETE Handler
|
115
|
+
#
|
116
|
+
# @param [String] resource_path This is the API resource you wish to interact
|
117
|
+
# with. Be sure to include your domain, where necessary.
|
118
|
+
# @return [Mailgun::Response] A Mailgun::Response object.
|
119
|
+
|
120
|
+
def delete(resource_path)
|
121
|
+
begin
|
122
|
+
response = @http_client[resource_path].delete()
|
123
|
+
Response.new(response)
|
124
|
+
rescue Exception => e
|
125
|
+
raise CommunicationError.new(e), e.response
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
# Converts MIME string to file for easy uploading to API
|
132
|
+
#
|
133
|
+
# @param [String] string MIME string to post to API
|
134
|
+
# @return [File] File object
|
135
|
+
|
136
|
+
def convert_string_to_file(string)
|
137
|
+
file = Tempfile.new('MG_TMP_MIME')
|
138
|
+
file.write(string)
|
139
|
+
file
|
140
|
+
end
|
141
|
+
|
142
|
+
# Generates the endpoint URL to for the API. Allows overriding
|
143
|
+
# API endpoint, API versions, and toggling SSL.
|
144
|
+
#
|
145
|
+
# @param [String] api_host URL endpoint the library will hit
|
146
|
+
# @param [String] api_version The version of the API to hit
|
147
|
+
# @param [Boolean] ssl True, SSL. False, No SSL.
|
148
|
+
# @return [string] concatenated URL string
|
149
|
+
|
150
|
+
def endpoint_generator(api_host, api_version, ssl)
|
151
|
+
ssl ? scheme = 'https' : scheme = 'http'
|
152
|
+
if api_version
|
153
|
+
"#{scheme}://#{api_host}/#{api_version}"
|
154
|
+
else
|
155
|
+
"#{scheme}://#{api_host}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# A Mailgun::Response object is instantiated for each response generated
|
161
|
+
# by the Client request. The Response object supports deserialization of
|
162
|
+
# the JSON result. Or, if you prefer JSON or YAML formatting, call the
|
163
|
+
# method for conversion.
|
164
|
+
#
|
165
|
+
# See the Github documentation for full examples.
|
166
|
+
|
167
|
+
class Response
|
168
|
+
|
169
|
+
attr_accessor :body
|
170
|
+
attr_accessor :code
|
171
|
+
|
172
|
+
def initialize(response)
|
173
|
+
@body = response.body
|
174
|
+
@code = response.code
|
175
|
+
end
|
176
|
+
|
177
|
+
# Return response as Ruby Hash
|
178
|
+
#
|
179
|
+
# @return [Hash] A standard Ruby Hash containing the HTTP result.
|
180
|
+
|
181
|
+
def to_h
|
182
|
+
begin
|
183
|
+
JSON.parse(@body)
|
184
|
+
rescue Exception => e
|
185
|
+
raise ParseError.new(e), e
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Replace @body with Ruby Hash
|
190
|
+
#
|
191
|
+
# @return [Hash] A standard Ruby Hash containing the HTTP result.
|
192
|
+
|
193
|
+
def to_h!
|
194
|
+
begin
|
195
|
+
@body = JSON.parse(@body)
|
196
|
+
rescue Exception => e
|
197
|
+
raise ParseError.new(e), e
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Return response as Yaml
|
202
|
+
#
|
203
|
+
# @return [String] A string containing response as YAML
|
204
|
+
|
205
|
+
def to_yaml
|
206
|
+
begin
|
207
|
+
YAML::dump(JSON.parse(@body))
|
208
|
+
rescue Exception => e
|
209
|
+
raise ParseError.new(e), e
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Replace @body with YAML
|
214
|
+
#
|
215
|
+
# @return [String] A string containing response as YAML
|
216
|
+
|
217
|
+
def to_yaml!
|
218
|
+
begin
|
219
|
+
@body = YAML::dump(JSON.parse(@body))
|
220
|
+
rescue Exception => e
|
221
|
+
raise ParseError.new(e), e
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Mailgun
|
2
|
+
|
3
|
+
class ParameterError < RuntimeError
|
4
|
+
attr_reader :object
|
5
|
+
|
6
|
+
def initialize(message=nil, object=nil)
|
7
|
+
@object = object
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class CommunicationError < RuntimeError
|
12
|
+
attr_reader :object
|
13
|
+
|
14
|
+
def initialize(message=nil, object=nil)
|
15
|
+
@object = object
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class ParseError < RuntimeError
|
20
|
+
attr_reader :object
|
21
|
+
|
22
|
+
def initialize(message=nil, object=nil)
|
23
|
+
@object = object
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'json'
|
3
|
+
require 'uri'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
module Mailgun
|
7
|
+
|
8
|
+
class OptInHandler
|
9
|
+
|
10
|
+
# Generates a hash that can be used to validate opt-in recipients. Encodes
|
11
|
+
# all the necessary data in the URL.
|
12
|
+
#
|
13
|
+
# @param [String] mailing_list The mailing list the user should be subscribed to.
|
14
|
+
# @param [String] secret_app_id A secret passphrase used as a constant for the hash.
|
15
|
+
# @param [Hash] recipient_address The address of the user that should be subscribed.
|
16
|
+
# @return [String] A url encoded URL suffix hash.
|
17
|
+
|
18
|
+
def self.generate_hash(mailing_list, secret_app_id, recipient_address)
|
19
|
+
hash = {'s' => Digest::MD5.hexdigest("#{secret_app_id}#{recipient_address}"),
|
20
|
+
'l' => mailing_list,
|
21
|
+
'r' => recipient_address}
|
22
|
+
|
23
|
+
URI.escape(Base64.encode64(JSON.generate(hash)))
|
24
|
+
end
|
25
|
+
|
26
|
+
# Validates the hash provided from the generate_hash method.
|
27
|
+
#
|
28
|
+
# @param [String] secret_app_id A secret passphrase used as a constant for the hash.
|
29
|
+
# @param [Hash] unique_hash The hash from the user. Likely via link click.
|
30
|
+
# @return [Hash or Boolean] A hash with 'recipient_address' and 'mailing_list', if validates. Otherwise, boolean false.
|
31
|
+
|
32
|
+
def self.validate_hash(secret_app_id, unique_hash)
|
33
|
+
url_parameters = JSON.parse(Base64.decode64(URI.unescape(unique_hash)))
|
34
|
+
generated_hash = Digest::MD5.hexdigest("#{secret_app_id}#{url_parameters['r']}")
|
35
|
+
hash_provided = url_parameters['s']
|
36
|
+
|
37
|
+
if(generated_hash == hash_provided)
|
38
|
+
return {'recipient_address' => url_parameters['r'], 'mailing_list' => url_parameters['l']}
|
39
|
+
else
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'mailgun/messages/message_builder'
|
2
|
+
require 'mailgun'
|
3
|
+
require "mailgun/exceptions/exceptions"
|
4
|
+
|
5
|
+
|
6
|
+
module Mailgun
|
7
|
+
|
8
|
+
# A Mailgun::BatchMessage object is used to create a valid payload
|
9
|
+
# for Batch Sending. Batch Sending can be difficult to implement, therefore
|
10
|
+
# this code makes it dead simple to send millions of messages in batches of
|
11
|
+
# 1,000 recipients per HTTP call.
|
12
|
+
#
|
13
|
+
# For the curious, the class simply keeps track of recipient data (count,
|
14
|
+
# user variables), and fires the API payload on the 1,000th addition of a recipient.
|
15
|
+
#
|
16
|
+
# The best way to use this class is:
|
17
|
+
# 1. Build your message using the Message Builder methods.
|
18
|
+
# 2. Query your source and create an iterable list.
|
19
|
+
# 3. Iterate through your source data, and add your recipients using the
|
20
|
+
# add_recipient() method.
|
21
|
+
# 4. Call finalize() to flush any remaining recipients and obtain/store
|
22
|
+
# the message_ids for tracking purposes.
|
23
|
+
#
|
24
|
+
# See the Github documentation for full examples.
|
25
|
+
|
26
|
+
class BatchMessage < MessageBuilder
|
27
|
+
|
28
|
+
attr_reader :message_ids, :domain, :recipient_variables
|
29
|
+
|
30
|
+
def initialize(client, domain)
|
31
|
+
@client = client
|
32
|
+
@recipient_variables = {}
|
33
|
+
@domain = domain
|
34
|
+
@message_ids = {}
|
35
|
+
super()
|
36
|
+
end
|
37
|
+
|
38
|
+
# Adds a specific type of recipient to the batch message object.
|
39
|
+
#
|
40
|
+
# @param [String] recipient_type The type of recipient. "to".
|
41
|
+
# @param [String] address The email address of the recipient to add to the message object.
|
42
|
+
# @param [Hash] variables A hash of the variables associated with the recipient. We recommend "first" and "last" at a minimum!
|
43
|
+
# @return [void]
|
44
|
+
|
45
|
+
def add_recipient(recipient_type, address, variables=nil)
|
46
|
+
if (@counters[:recipients][recipient_type] == 1000)
|
47
|
+
send_message(@message)
|
48
|
+
end
|
49
|
+
|
50
|
+
compiled_address = parse_address(address, variables)
|
51
|
+
@message[recipient_type] = compiled_address
|
52
|
+
if recipient_type != :from
|
53
|
+
store_recipient_variables(recipient_type, address, variables)
|
54
|
+
end
|
55
|
+
if @counters[:recipients].has_key?(recipient_type)
|
56
|
+
@counters[:recipients][recipient_type] += 1
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Always call this function after adding recipients. If less than 1000 are added,
|
61
|
+
# this function will ensure the batch is sent.
|
62
|
+
#
|
63
|
+
# @return [Hash] A hash of {'Message ID' => '# of Messages Sent'}
|
64
|
+
|
65
|
+
def finalize()
|
66
|
+
if any_recipients_left?
|
67
|
+
send_message(@message)
|
68
|
+
end
|
69
|
+
@message_ids
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# This method determines if it's necessary to send another batch.
|
75
|
+
#
|
76
|
+
# @return [Boolean]
|
77
|
+
|
78
|
+
def any_recipients_left?
|
79
|
+
if @counters[:recipients][:to] > 0
|
80
|
+
return true
|
81
|
+
elsif @counters[:recipients][:cc] > 0
|
82
|
+
return true
|
83
|
+
elsif @counters[:recipients][:bcc] > 0
|
84
|
+
return true
|
85
|
+
else
|
86
|
+
return false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# This method initiates a batch send to the API. It formats the recipient
|
91
|
+
# variables, posts to the API, gathers the message IDs, then flushes that data
|
92
|
+
# to prepare for the next batch. This method implements the Mailgun Client, thus,
|
93
|
+
# an exception will be thrown if a communication error occurs.
|
94
|
+
#
|
95
|
+
# @return [Boolean]
|
96
|
+
|
97
|
+
def send_message(message)
|
98
|
+
@message["recipient-variables"] = JSON.generate(@recipient_variables)
|
99
|
+
response = @client.send_message(@domain, @message).to_h!
|
100
|
+
message_id = response['id'].gsub(/\>|\</, '')
|
101
|
+
@message_ids[message_id] = count_recipients()
|
102
|
+
reset_message()
|
103
|
+
end
|
104
|
+
|
105
|
+
# This method stores recipient variables for each recipient added, if
|
106
|
+
# variables exist.
|
107
|
+
|
108
|
+
def store_recipient_variables(recipient_type, address, variables)
|
109
|
+
if not variables
|
110
|
+
variables = {:id => @counters[:recipients][recipient_type]}
|
111
|
+
end
|
112
|
+
@recipient_variables[address] = variables
|
113
|
+
end
|
114
|
+
|
115
|
+
# This method stores recipient variables for each recipient added, if
|
116
|
+
# variables exist.
|
117
|
+
|
118
|
+
def count_recipients()
|
119
|
+
count = 0
|
120
|
+
@counters[:recipients].each { |a| count = a[1] + count}
|
121
|
+
count
|
122
|
+
end
|
123
|
+
|
124
|
+
# This method resets the message object to prepare for the next batch
|
125
|
+
# of recipients.
|
126
|
+
|
127
|
+
def reset_message()
|
128
|
+
@message.delete("recipient-variables")
|
129
|
+
@message.delete(:to)
|
130
|
+
@message.delete(:cc)
|
131
|
+
@message.delete(:bcc)
|
132
|
+
@counters[:recipients][:to] = 0
|
133
|
+
@counters[:recipients][:cc] = 0
|
134
|
+
@counters[:recipients][:bcc] = 0
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
@@ -0,0 +1,321 @@
|
|
1
|
+
require 'multimap'
|
2
|
+
require 'time'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Mailgun
|
6
|
+
|
7
|
+
# A Mailgun::MessageBuilder object is used to create a valid payload
|
8
|
+
# for the Mailgun API messages endpoint. If you prefer step by step message
|
9
|
+
# generation through your code, this class is for you.
|
10
|
+
#
|
11
|
+
# See the Github documentation for full examples.
|
12
|
+
|
13
|
+
class MessageBuilder
|
14
|
+
|
15
|
+
attr_reader :message, :counters
|
16
|
+
|
17
|
+
def initialize()
|
18
|
+
@message = Multimap.new
|
19
|
+
@counters = {:recipients =>
|
20
|
+
{:to => 0,
|
21
|
+
:cc => 0,
|
22
|
+
:bcc => 0},
|
23
|
+
:attributes =>
|
24
|
+
{:attachment => 0,
|
25
|
+
:campaign_id => 0,
|
26
|
+
:custom_option => 0,
|
27
|
+
:tag => 0}}
|
28
|
+
end
|
29
|
+
|
30
|
+
# Adds a specific type of recipient to the message object.
|
31
|
+
#
|
32
|
+
# @param [String] recipient_type The type of recipient. "to", "cc", "bcc" or "h:reply-to".
|
33
|
+
# @param [String] address The email address of the recipient to add to the message object.
|
34
|
+
# @param [Hash] variables A hash of the variables associated with the recipient. We recommend "first" and "last" at a minimum!
|
35
|
+
# @return [void]
|
36
|
+
|
37
|
+
def add_recipient(recipient_type, address, variables=nil)
|
38
|
+
if (@counters[:recipients][recipient_type] == 1000)
|
39
|
+
raise ParameterError.new("Too many recipients added to message.", address)
|
40
|
+
end
|
41
|
+
|
42
|
+
compiled_address = parse_address(address, variables)
|
43
|
+
@message[recipient_type] = compiled_address
|
44
|
+
|
45
|
+
if @counters[:recipients].has_key?(recipient_type)
|
46
|
+
@counters[:recipients][recipient_type] += 1
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Sets the from address for the message
|
51
|
+
#
|
52
|
+
# @param [String] address The address of the sender.
|
53
|
+
# @param [Hash] variables A hash of the variables associated with the recipient. We recommend "first" and "last" at a minimum!
|
54
|
+
# @return [void]
|
55
|
+
|
56
|
+
def set_from_address(address, variables=nil)
|
57
|
+
add_recipient("from", address, variables)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Set a subject for the message object
|
61
|
+
#
|
62
|
+
# @param [String] subject The subject for the email.
|
63
|
+
# @return [void]
|
64
|
+
|
65
|
+
def set_subject(subject=nil)
|
66
|
+
simple_setter(:subject, subject)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Set a text body for the message object
|
70
|
+
#
|
71
|
+
# @param [String] text_body The text body for the email.
|
72
|
+
# @return [void]
|
73
|
+
|
74
|
+
def set_text_body(text_body=nil)
|
75
|
+
simple_setter(:text, text_body)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Set a html body for the message object
|
79
|
+
#
|
80
|
+
# @param [String] html_body The html body for the email.
|
81
|
+
# @return [void]
|
82
|
+
|
83
|
+
def set_html_body(html_body=nil)
|
84
|
+
simple_setter(:html, html_body)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Adds a series of attachments, when called upon.
|
88
|
+
#
|
89
|
+
# @param [String] attachment A file object for attaching as an attachment.
|
90
|
+
# @param [String] filename The filename you wish the attachment to be.
|
91
|
+
# @return [void]
|
92
|
+
|
93
|
+
def add_attachment(attachment, filename=nil)
|
94
|
+
if attachment.is_a?(String)
|
95
|
+
attachment = File.open(attachment, "r")
|
96
|
+
end
|
97
|
+
if !attachment.is_a?(File) || !attachment.respond_to?(:read)
|
98
|
+
raise ParameterError.new("Unable to access attachment file object.")
|
99
|
+
end
|
100
|
+
if !filename.nil?
|
101
|
+
attachment.instance_eval "def original_filename; '#{filename}'; end"
|
102
|
+
end
|
103
|
+
@message[:attachment] = attachment
|
104
|
+
end
|
105
|
+
|
106
|
+
# Adds an inline image to the mesage object.
|
107
|
+
#
|
108
|
+
# @param [String] inline_image A file object for attaching an inline image.
|
109
|
+
# @param [String] filename The filename you wish the inline image to be.
|
110
|
+
# @return [void]
|
111
|
+
|
112
|
+
def add_inline_image(inline_image, filename=nil)
|
113
|
+
if inline_image.is_a?(String)
|
114
|
+
inline_image = File.open(inline_image, "r")
|
115
|
+
end
|
116
|
+
if !inline_image.is_a?(File) || !inline_image.respond_to?(:read)
|
117
|
+
raise ParameterError.new("Unable to access attachment file object.")
|
118
|
+
end
|
119
|
+
if !filename.nil?
|
120
|
+
inline_image.instance_eval "def original_filename; '#{filename}'; end"
|
121
|
+
end
|
122
|
+
@message[:inline] = inline_image
|
123
|
+
end
|
124
|
+
|
125
|
+
# Send a message in test mode. (The message won't really be sent to the recipient)
|
126
|
+
#
|
127
|
+
# @param [Boolean] test_mode The boolean or string value (will fix itself)
|
128
|
+
# @return [void]
|
129
|
+
|
130
|
+
def set_test_mode(test_mode)
|
131
|
+
simple_setter("o:testmode", bool_lookup(test_mode))
|
132
|
+
end
|
133
|
+
|
134
|
+
# Turn DKIM on or off per message
|
135
|
+
#
|
136
|
+
# @param [Boolean] dkim The boolean or string value(will fix itself)
|
137
|
+
# @return [void]
|
138
|
+
|
139
|
+
def set_dkim(dkim)
|
140
|
+
simple_setter("o:dkim", bool_lookup(dkim))
|
141
|
+
end
|
142
|
+
|
143
|
+
# Add campaign IDs to message. Limit of 3 per message.
|
144
|
+
#
|
145
|
+
# @param [String] campaign_id A defined campaign ID to add to the message.
|
146
|
+
# @return [void]
|
147
|
+
|
148
|
+
def add_campaign_id(campaign_id)
|
149
|
+
if (@counters[:attributes][:campaign_id] == 3)
|
150
|
+
raise ParameterError.new("Too many campaigns added to message.", campaign_id)
|
151
|
+
end
|
152
|
+
@message["o:campaign"] = campaign_id
|
153
|
+
@counters[:attributes][:campaign_id] += 1
|
154
|
+
end
|
155
|
+
|
156
|
+
# Add tags to message. Limit of 3 per message.
|
157
|
+
#
|
158
|
+
# @param [String] tag A defined campaign ID to add to the message.
|
159
|
+
# @return [void]
|
160
|
+
|
161
|
+
def add_tag(tag)
|
162
|
+
if (@counters[:attributes][:tag] == 3)
|
163
|
+
raise ParameterError.new("Too many tags added to message.", tag)
|
164
|
+
end
|
165
|
+
@message["o:tag"] = tag
|
166
|
+
@counters[:attributes][:tag] += 1
|
167
|
+
end
|
168
|
+
|
169
|
+
# Turn Open Tracking on and off, on a per message basis.
|
170
|
+
#
|
171
|
+
# @param [Boolean] tracking Boolean true or false.
|
172
|
+
# @return [void]
|
173
|
+
|
174
|
+
def set_open_tracking(tracking)
|
175
|
+
simple_setter("o:tracking-opens", bool_lookup(tracking))
|
176
|
+
end
|
177
|
+
|
178
|
+
# Turn Click Tracking on and off, on a per message basis.
|
179
|
+
#
|
180
|
+
# @param [String] tracking True, False, or HTML (for HTML only tracking)
|
181
|
+
# @return [void]
|
182
|
+
|
183
|
+
def set_click_tracking(tracking)
|
184
|
+
simple_setter("o:tracking-clicks", bool_lookup(tracking))
|
185
|
+
end
|
186
|
+
|
187
|
+
# Enable Delivery delay on message. Specify an RFC2822 date, and Mailgun
|
188
|
+
# will not deliver the message until that date/time. For conversion
|
189
|
+
# options, see Ruby "Time". Example: "October 25, 2013 10:00PM CST" will
|
190
|
+
# be converted to "Fri, 25 Oct 2013 22:00:00 -0600".
|
191
|
+
#
|
192
|
+
# @param [String] timestamp A date and time, including a timezone.
|
193
|
+
# @return [void]
|
194
|
+
|
195
|
+
def set_delivery_time(timestamp)
|
196
|
+
time_str = DateTime.parse(timestamp)
|
197
|
+
simple_setter("o:deliverytime", time_str.rfc2822)
|
198
|
+
end
|
199
|
+
|
200
|
+
# Add custom data to the message. The data should be either a hash or JSON
|
201
|
+
# encoded. The custom data will be added as a header to your message.
|
202
|
+
#
|
203
|
+
# @param [string] name A name for the custom data. (Ex. X-Mailgun-<Name of Data>: {})
|
204
|
+
# @param [Hash] data Either a hash or JSON string.
|
205
|
+
# @return [void]
|
206
|
+
|
207
|
+
def set_custom_data(name, data)
|
208
|
+
if data.is_a?(Hash)
|
209
|
+
data = data.to_json
|
210
|
+
elsif data.is_a?(String)
|
211
|
+
if not valid_json?(data)
|
212
|
+
begin
|
213
|
+
data = JSON.generate(data)
|
214
|
+
rescue
|
215
|
+
raise ParameterError.new("Failed to parse provided JSON.", data)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
simple_setter("v:#{name}", data)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Add custom parameter to the message. A custom parameter is any parameter that
|
223
|
+
# is not yet supported by the SDK, but available at the API. Note: No validation
|
224
|
+
# is performed. Don't forget to prefix the parameter with o, h, or v.
|
225
|
+
#
|
226
|
+
# @param [string] name A name for the custom parameter.
|
227
|
+
# @param [string] data A string of data for the paramter.
|
228
|
+
# @return [void]
|
229
|
+
|
230
|
+
def add_custom_parameter(name, data)
|
231
|
+
@message[name] = data
|
232
|
+
end
|
233
|
+
|
234
|
+
private
|
235
|
+
|
236
|
+
# Sets values within the multidict, however, prevents
|
237
|
+
# duplicate values for keys.
|
238
|
+
#
|
239
|
+
# @param [String] parameter The message object parameter name.
|
240
|
+
# @param [String] value The value of the parameter.
|
241
|
+
# @return [void]
|
242
|
+
|
243
|
+
def simple_setter(parameter, value)
|
244
|
+
if value.nil?
|
245
|
+
if @message.include?(parameter)
|
246
|
+
@message.replace({parameter => ''})
|
247
|
+
else
|
248
|
+
@message[parameter] = ''
|
249
|
+
end
|
250
|
+
else
|
251
|
+
if @message.include?(parameter)
|
252
|
+
@message.replace({parameter => value})
|
253
|
+
else
|
254
|
+
@message[parameter] = value
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Converts boolean type to string
|
260
|
+
#
|
261
|
+
# @param [String] value The item to convert
|
262
|
+
# @return [void]
|
263
|
+
|
264
|
+
def bool_lookup(value)
|
265
|
+
if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
266
|
+
return value ? "yes" : "no"
|
267
|
+
elsif value.is_a?(String)
|
268
|
+
value.downcase!
|
269
|
+
if ['true', 'yes', 'yep'].include? value
|
270
|
+
return "yes"
|
271
|
+
elsif ['false', 'no', 'nope'].include? value
|
272
|
+
return "no"
|
273
|
+
else
|
274
|
+
return value
|
275
|
+
end
|
276
|
+
else
|
277
|
+
return value
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# Validates whether the input is JSON.
|
282
|
+
#
|
283
|
+
# @param [String] json_ The suspected JSON string.
|
284
|
+
# @return [void]
|
285
|
+
|
286
|
+
def valid_json? json_
|
287
|
+
JSON.parse(json_)
|
288
|
+
return true
|
289
|
+
rescue JSON::ParserError
|
290
|
+
return false
|
291
|
+
end
|
292
|
+
|
293
|
+
# Parses the address and gracefully handles any
|
294
|
+
# missing parameters. The result should be something like:
|
295
|
+
# "'First Last' <person@domain.com>"
|
296
|
+
#
|
297
|
+
# @param [String] address The email address to parse.
|
298
|
+
# @param [Hash] variables A list of recipient variables.
|
299
|
+
# @return [void]
|
300
|
+
|
301
|
+
def parse_address(address, variables)
|
302
|
+
if variables.nil?
|
303
|
+
return address
|
304
|
+
end
|
305
|
+
first, last = ''
|
306
|
+
if variables.has_key?('first')
|
307
|
+
first = variables['first']
|
308
|
+
if variables.has_key?('last')
|
309
|
+
last = variables['last']
|
310
|
+
end
|
311
|
+
full_name = "#{first} #{last}".strip
|
312
|
+
end
|
313
|
+
if defined?(full_name)
|
314
|
+
return "'#{full_name}' <#{address}>"
|
315
|
+
end
|
316
|
+
return address
|
317
|
+
end
|
318
|
+
|
319
|
+
end
|
320
|
+
|
321
|
+
end
|