wj-mailgun-ruby 1.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rubocop.yml +8 -0
- data/.rubocop_todo.yml +22 -0
- data/.ruby-env.yml.example +12 -0
- data/.ruby-version +1 -0
- data/.travis.yml +24 -0
- data/Gemfile +6 -0
- data/LICENSE +191 -0
- data/README.md +241 -0
- data/Rakefile +35 -0
- data/docs/Domains.md +54 -0
- data/docs/Events.md +46 -0
- data/docs/MessageBuilder.md +105 -0
- data/docs/Messages.md +107 -0
- data/docs/OptInHandler.md +103 -0
- data/docs/Snippets.md +526 -0
- data/docs/Suppressions.md +82 -0
- data/docs/Webhooks.md +40 -0
- data/lib/mailgun-ruby.rb +2 -0
- data/lib/mailgun.rb +39 -0
- data/lib/mailgun/address.rb +45 -0
- data/lib/mailgun/chains.rb +16 -0
- data/lib/mailgun/client.rb +199 -0
- data/lib/mailgun/domains/domains.rb +84 -0
- data/lib/mailgun/events/events.rb +120 -0
- data/lib/mailgun/exceptions/exceptions.rb +65 -0
- data/lib/mailgun/lists/opt_in_handler.rb +58 -0
- data/lib/mailgun/messages/batch_message.rb +125 -0
- data/lib/mailgun/messages/message_builder.rb +413 -0
- data/lib/mailgun/response.rb +62 -0
- data/lib/mailgun/suppressions.rb +270 -0
- data/lib/mailgun/version.rb +4 -0
- data/lib/mailgun/webhooks/webhooks.rb +101 -0
- data/lib/railgun.rb +8 -0
- data/lib/railgun/attachment.rb +56 -0
- data/lib/railgun/errors.rb +27 -0
- data/lib/railgun/mailer.rb +161 -0
- data/lib/railgun/message.rb +17 -0
- data/lib/railgun/railtie.rb +9 -0
- data/mailgun.gemspec +37 -0
- data/spec/integration/bounces_spec.rb +44 -0
- data/spec/integration/campaign_spec.rb +60 -0
- data/spec/integration/complaints_spec.rb +38 -0
- data/spec/integration/domains_spec.rb +39 -0
- data/spec/integration/email_validation_spec.rb +57 -0
- data/spec/integration/events_spec.rb +28 -0
- data/spec/integration/list_members_spec.rb +63 -0
- data/spec/integration/list_spec.rb +58 -0
- data/spec/integration/mailgun_spec.rb +121 -0
- data/spec/integration/messages/sample_data/mime.txt +38 -0
- data/spec/integration/routes_spec.rb +74 -0
- data/spec/integration/stats_spec.rb +15 -0
- data/spec/integration/suppressions_spec.rb +126 -0
- data/spec/integration/unsubscribes_spec.rb +42 -0
- data/spec/integration/webhook_spec.rb +54 -0
- data/spec/spec_helper.rb +45 -0
- data/spec/unit/connection/test_client.rb +99 -0
- data/spec/unit/events/events_spec.rb +50 -0
- data/spec/unit/lists/opt_in_handler_spec.rb +24 -0
- data/spec/unit/mailgun_spec.rb +127 -0
- data/spec/unit/messages/batch_message_spec.rb +131 -0
- data/spec/unit/messages/message_builder_spec.rb +584 -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
- data/vcr_cassettes/bounces.yml +175 -0
- data/vcr_cassettes/complaints.yml +175 -0
- data/vcr_cassettes/domains.todo.yml +42 -0
- data/vcr_cassettes/domains.yml +360 -0
- data/vcr_cassettes/email_validation.yml +167 -0
- data/vcr_cassettes/events.yml +108 -0
- data/vcr_cassettes/exceptions.yml +45 -0
- data/vcr_cassettes/list_members.yml +320 -0
- data/vcr_cassettes/mailing_list.todo.yml +43 -0
- data/vcr_cassettes/mailing_list.yml +390 -0
- data/vcr_cassettes/routes.yml +359 -0
- data/vcr_cassettes/send_message.yml +107 -0
- data/vcr_cassettes/stats.yml +44 -0
- data/vcr_cassettes/suppressions.yml +676 -0
- data/vcr_cassettes/unsubscribes.yml +191 -0
- data/vcr_cassettes/webhooks.yml +276 -0
- metadata +263 -0
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'mailgun/exceptions/exceptions'
|
2
|
+
|
3
|
+
module Mailgun
|
4
|
+
|
5
|
+
# A Mailgun::Events object makes it really simple to consume
|
6
|
+
# Mailgun's events from the Events endpoint.
|
7
|
+
#
|
8
|
+
# This is not yet comprehensive.
|
9
|
+
#
|
10
|
+
# Examples
|
11
|
+
#
|
12
|
+
# See the Github documentation for full examples.
|
13
|
+
class Events
|
14
|
+
include Enumerable
|
15
|
+
|
16
|
+
# Public: event initializer
|
17
|
+
#
|
18
|
+
# client - an instance of Mailgun::Client
|
19
|
+
# domain - the domain to build queries
|
20
|
+
def initialize(client, domain)
|
21
|
+
@client = client
|
22
|
+
@domain = domain
|
23
|
+
@paging_next = nil
|
24
|
+
@paging_previous = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
# Public: Issues a simple get against the client. Alias of `next`.
|
28
|
+
#
|
29
|
+
# params - a Hash of query options and/or filters.
|
30
|
+
#
|
31
|
+
# Returns a Mailgun::Response object.
|
32
|
+
def get(params = nil)
|
33
|
+
self.next(params)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Public: Using built in paging, obtains the next set of data.
|
37
|
+
# If an events request hasn't been sent previously, this will send one
|
38
|
+
# without parameters
|
39
|
+
#
|
40
|
+
# params - a Hash of query options and/or filters.
|
41
|
+
#
|
42
|
+
# Returns a Mailgun::Response object.
|
43
|
+
def next(params = nil)
|
44
|
+
get_events(params, @paging_next)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Public: Using built in paging, obtains the previous set of data.
|
48
|
+
# If an events request hasn't been sent previously, this will send one
|
49
|
+
# without parameters
|
50
|
+
#
|
51
|
+
# params - a Hash of query options and/or filters.
|
52
|
+
#
|
53
|
+
# Returns Mailgun::Response object.
|
54
|
+
def previous(params = nil)
|
55
|
+
get_events(params, @paging_previous)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Public: Allows iterating through all events and performs automatic paging.
|
59
|
+
#
|
60
|
+
# &block - Block to execute on items.
|
61
|
+
def each(&block)
|
62
|
+
items = self.next.to_h['items']
|
63
|
+
|
64
|
+
until items.empty?
|
65
|
+
items.each(&block)
|
66
|
+
items = self.next.to_h['items']
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
# Internal: Makes and processes the event request through the client
|
73
|
+
#
|
74
|
+
# params - optional Hash of query options
|
75
|
+
# paging - the URL key used for previous/next requests
|
76
|
+
#
|
77
|
+
# Returns a Mailgun.Response object.
|
78
|
+
def get_events(params = nil, paging = nil)
|
79
|
+
response = @client.get(construct_url(paging), params)
|
80
|
+
extract_paging(response)
|
81
|
+
response
|
82
|
+
end
|
83
|
+
|
84
|
+
# Internal: given an event response, pull and store the paging keys
|
85
|
+
#
|
86
|
+
# response - a Mailgun::Response object
|
87
|
+
#
|
88
|
+
# Return is irrelevant.
|
89
|
+
def extract_paging(response)
|
90
|
+
paging = response.to_h['paging']
|
91
|
+
next_page_url = paging && paging['next'] # gives nil when any one of the keys doens't exist
|
92
|
+
previous_page_url = paging && paging['previous'] # can be replaced with Hash#dig for ruby >= 2.3.0
|
93
|
+
@paging_next = extract_endpoint_from(next_page_url)
|
94
|
+
@paging_previous = extract_endpoint_from(previous_page_url)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Internal: given a paging URL, extract the endpoint
|
98
|
+
#
|
99
|
+
# response - the endpoint for the previous/next page
|
100
|
+
#
|
101
|
+
# Returns a String of the partial URI if the given url follows the regular API format
|
102
|
+
# Returns nil in other cases (e.g. when given nil, or an irrelevant url)
|
103
|
+
def extract_endpoint_from(url = nil)
|
104
|
+
URI.parse(url).path[/api.mailgun.net\/v[\d]\/#{@domain}\/events\/(.+)/,1]
|
105
|
+
rescue URI::InvalidURIError
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
|
109
|
+
# Internal: construct the event path to be used by the client
|
110
|
+
#
|
111
|
+
# paging - the URL key for previous/next set of results
|
112
|
+
#
|
113
|
+
# Returns a String of the partial URI
|
114
|
+
def construct_url(paging = nil)
|
115
|
+
return "#{@domain}/events/#{paging}" if paging
|
116
|
+
"#{@domain}/events"
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Mailgun
|
2
|
+
|
3
|
+
# Public: A basic class for mananging errors.
|
4
|
+
# Inherits from StandardError (previously RuntimeError) as not all errors are
|
5
|
+
# runtime errors.
|
6
|
+
class Error < StandardError
|
7
|
+
|
8
|
+
# Public: get an object an error is instantiated with
|
9
|
+
attr_reader :object
|
10
|
+
|
11
|
+
# Public: initialize a Mailgun:Error object
|
12
|
+
#
|
13
|
+
# message - a String describing the error
|
14
|
+
# object - an object with details about the error
|
15
|
+
def initialize(message = nil, object = nil)
|
16
|
+
super(message)
|
17
|
+
@object = object
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Public: Class for managing parameter errors, with a pretty name.
|
22
|
+
# Inherits from Mailgun::Error
|
23
|
+
class ParameterError < Error; end
|
24
|
+
|
25
|
+
# Public: Class for managing parsing errors, with a pretty name.
|
26
|
+
# Inherits from Mailgun::Error
|
27
|
+
class ParseError < Error; end
|
28
|
+
|
29
|
+
# Public: Class for managing communications (eg http) response errors
|
30
|
+
# Inherits from Mailgun::Error
|
31
|
+
class CommunicationError < Error
|
32
|
+
# Public: gets HTTP status code
|
33
|
+
attr_reader :code
|
34
|
+
|
35
|
+
# Public: fallback if there is no response code on the object
|
36
|
+
NOCODE = 000
|
37
|
+
|
38
|
+
# Public: initialization of new error given a message and/or object
|
39
|
+
#
|
40
|
+
# message - a String detailing the error
|
41
|
+
# response - a RestClient::Response object
|
42
|
+
#
|
43
|
+
def initialize(message = nil, response = nil)
|
44
|
+
@response = response
|
45
|
+
@code = response.code || NOCODE
|
46
|
+
|
47
|
+
begin
|
48
|
+
api_message = JSON.parse(response.body)['message']
|
49
|
+
rescue JSON::ParserError
|
50
|
+
api_message = response.body
|
51
|
+
rescue NoMethodError
|
52
|
+
api_message = "Unknown API error"
|
53
|
+
end
|
54
|
+
|
55
|
+
message = message || ''
|
56
|
+
message = message + ': ' + api_message
|
57
|
+
|
58
|
+
super(message, response)
|
59
|
+
rescue NoMethodError, JSON::ParserError
|
60
|
+
@code = NOCODE
|
61
|
+
super(message, response)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'base64'
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module Mailgun
|
6
|
+
|
7
|
+
# Public: Provides methods for creating and handling opt-in URLs,
|
8
|
+
# particularlly for mailing lists.
|
9
|
+
#
|
10
|
+
# See: https://github.com/mailgun/mailgun-ruby/blob/master/OptInHandler.md
|
11
|
+
class OptInHandler
|
12
|
+
|
13
|
+
# Generates a hash that can be used to validate opt-in recipients. Encodes
|
14
|
+
# all the necessary data in the URL.
|
15
|
+
#
|
16
|
+
# @param [String] mailing_list The mailing list the user should be subscribed to.
|
17
|
+
# @param [String] secret_app_id A secret passphrase used as a constant for the hash.
|
18
|
+
# @param [Hash] recipient_address The address of the user that should be subscribed.
|
19
|
+
# @return [String] A url encoded URL suffix hash.
|
20
|
+
def self.generate_hash(mailing_list, secret_app_id, recipient_address)
|
21
|
+
inner_payload = { 'l' => mailing_list, 'r' => recipient_address }
|
22
|
+
|
23
|
+
inner_payload_encoded = Base64.encode64(JSON.generate(inner_payload))
|
24
|
+
|
25
|
+
sha1_digest = OpenSSL::Digest.new('sha1')
|
26
|
+
digest = OpenSSL::HMAC.hexdigest(sha1_digest, secret_app_id, inner_payload_encoded)
|
27
|
+
|
28
|
+
outer_payload = { 'h' => digest, 'p' => inner_payload_encoded }
|
29
|
+
|
30
|
+
outer_payload_encoded = Base64.encode64(JSON.generate(outer_payload))
|
31
|
+
|
32
|
+
CGI.escape(outer_payload_encoded)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Validates the hash provided from the generate_hash method.
|
36
|
+
#
|
37
|
+
# @param [String] secret_app_id A secret passphrase used as a constant for the hash.
|
38
|
+
# @param [Hash] unique_hash The hash from the user. Likely via link click.
|
39
|
+
# @return [Hash or Boolean] A hash with 'recipient_address' and 'mailing_list', if validates. Otherwise, boolean false.
|
40
|
+
def self.validate_hash(secret_app_id, unique_hash)
|
41
|
+
outer_payload = JSON.parse(Base64.decode64(CGI.unescape(unique_hash)))
|
42
|
+
|
43
|
+
sha1_digest = OpenSSL::Digest.new('sha1')
|
44
|
+
generated_hash = OpenSSL::HMAC.hexdigest(sha1_digest, secret_app_id, outer_payload['p'])
|
45
|
+
|
46
|
+
inner_payload = JSON.parse(Base64.decode64(CGI.unescape(outer_payload['p'])))
|
47
|
+
|
48
|
+
hash_provided = outer_payload['h']
|
49
|
+
|
50
|
+
if generated_hash == hash_provided
|
51
|
+
return { 'recipient_address' => inner_payload['r'], 'mailing_list' => inner_payload['l'] }
|
52
|
+
end
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'mailgun/messages/message_builder'
|
2
|
+
|
3
|
+
module Mailgun
|
4
|
+
|
5
|
+
# A Mailgun::BatchMessage object is used to create a valid payload
|
6
|
+
# for Batch Sending. Batch Sending can be difficult to implement, therefore
|
7
|
+
# this code makes it dead simple to send millions of messages in batches of
|
8
|
+
# 1,000 recipients per HTTP call.
|
9
|
+
#
|
10
|
+
# For the curious, the class simply keeps track of recipient data (count,
|
11
|
+
# user variables), and fires the API payload on the 1,000th addition of a recipient.
|
12
|
+
#
|
13
|
+
# The best way to use this class is:
|
14
|
+
# 1. Build your message using the Message Builder methods.
|
15
|
+
# 2. Query your source and create an iterable list.
|
16
|
+
# 3. Iterate through your source data, and add your recipients using the
|
17
|
+
# add_recipient() method.
|
18
|
+
# 4. Call finalize() to flush any remaining recipients and obtain/store
|
19
|
+
# the message_ids for tracking purposes.
|
20
|
+
#
|
21
|
+
# See the Github documentation for full examples.
|
22
|
+
class BatchMessage < MessageBuilder
|
23
|
+
|
24
|
+
attr_reader :message_ids, :domain, :recipient_variables
|
25
|
+
|
26
|
+
# Public: Creates a new BatchMessage object.
|
27
|
+
def initialize(client, domain)
|
28
|
+
@client = client
|
29
|
+
@recipient_variables = {}
|
30
|
+
@domain = domain
|
31
|
+
@message_ids = {}
|
32
|
+
@message = Hash.new { |hash, key| hash[key] = [] }
|
33
|
+
|
34
|
+
@counters = {
|
35
|
+
recipients: { to: 0, cc: 0, bcc: 0 },
|
36
|
+
attributes: { attachment: 0, campaign_id: 0, custom_option: 0, tag: 0 }
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
# Adds a specific type of recipient to the batch message object.
|
41
|
+
#
|
42
|
+
# @param [String] recipient_type The type of recipient. "to".
|
43
|
+
# @param [String] address The email address of the recipient to add to the message object.
|
44
|
+
# @param [Hash] variables A hash of the variables associated with the recipient. We recommend "first" and "last" at a minimum!
|
45
|
+
# @return [void]
|
46
|
+
def add_recipient(recipient_type, address, variables = nil)
|
47
|
+
# send the message when we have 1000, not before
|
48
|
+
send_message if @counters[:recipients][recipient_type] == Mailgun::Chains::MAX_RECIPIENTS
|
49
|
+
|
50
|
+
compiled_address = parse_address(address, variables)
|
51
|
+
set_multi_complex(recipient_type, compiled_address)
|
52
|
+
|
53
|
+
store_recipient_variables(recipient_type, address, variables) if recipient_type != :from
|
54
|
+
|
55
|
+
@counters[:recipients][recipient_type] += 1 if @counters[:recipients].key?(recipient_type)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Always call this function after adding recipients. If less than 1000 are added,
|
59
|
+
# this function will ensure the batch is sent.
|
60
|
+
#
|
61
|
+
# @return [Hash] A hash of {'Message ID' => '# of Messages Sent'}
|
62
|
+
def finalize
|
63
|
+
send_message if any_recipients_left?
|
64
|
+
@message_ids
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# This method determines if it's necessary to send another batch.
|
70
|
+
#
|
71
|
+
# @return [Boolean]
|
72
|
+
def any_recipients_left?
|
73
|
+
return true if @counters[:recipients][:to] > 0
|
74
|
+
return true if @counters[:recipients][:cc] > 0
|
75
|
+
return true if @counters[:recipients][:bcc] > 0
|
76
|
+
false
|
77
|
+
end
|
78
|
+
|
79
|
+
# This method initiates a batch send to the API. It formats the recipient
|
80
|
+
# variables, posts to the API, gathers the message IDs, then flushes that data
|
81
|
+
# to prepare for the next batch. This method implements the Mailgun Client, thus,
|
82
|
+
# an exception will be thrown if a communication error occurs.
|
83
|
+
#
|
84
|
+
# @return [Boolean]
|
85
|
+
def send_message
|
86
|
+
rkey = 'recipient-variables'
|
87
|
+
set_multi_simple rkey, JSON.generate(@recipient_variables)
|
88
|
+
@message[rkey] = @message[rkey].first if @message.key?(rkey)
|
89
|
+
|
90
|
+
response = @client.send_message(@domain, @message).to_h!
|
91
|
+
message_id = response['id'].gsub(/\>|\</, '')
|
92
|
+
@message_ids[message_id] = count_recipients
|
93
|
+
reset_message
|
94
|
+
end
|
95
|
+
|
96
|
+
# This method stores recipient variables for each recipient added, if
|
97
|
+
# variables exist.
|
98
|
+
def store_recipient_variables(recipient_type, address, variables)
|
99
|
+
variables = { id: @counters[:recipients][recipient_type] } unless variables
|
100
|
+
@recipient_variables[address] = variables
|
101
|
+
end
|
102
|
+
|
103
|
+
# This method stores recipient variables for each recipient added, if
|
104
|
+
# variables exist.
|
105
|
+
def count_recipients
|
106
|
+
count = 0
|
107
|
+
@counters[:recipients].each_value { |cnt| count += cnt }
|
108
|
+
count
|
109
|
+
end
|
110
|
+
|
111
|
+
# This method resets the message object to prepare for the next batch
|
112
|
+
# of recipients.
|
113
|
+
def reset_message
|
114
|
+
@message.delete('recipient-variables')
|
115
|
+
@message.delete(:to)
|
116
|
+
@message.delete(:cc)
|
117
|
+
@message.delete(:bcc)
|
118
|
+
@counters[:recipients][:to] = 0
|
119
|
+
@counters[:recipients][:cc] = 0
|
120
|
+
@counters[:recipients][:bcc] = 0
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
@@ -0,0 +1,413 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module Mailgun
|
4
|
+
|
5
|
+
# A Mailgun::MessageBuilder object is used to create a valid payload
|
6
|
+
# for the Mailgun API messages endpoint. If you prefer step by step message
|
7
|
+
# generation through your code, this class is for you.
|
8
|
+
#
|
9
|
+
# See the Github documentation for full examples.
|
10
|
+
class MessageBuilder
|
11
|
+
|
12
|
+
attr_reader :message, :counters
|
13
|
+
|
14
|
+
# Public: Creates a new MessageBuilder object.
|
15
|
+
def initialize
|
16
|
+
@message = Hash.new { |hash, key| hash[key] = [] }
|
17
|
+
|
18
|
+
@counters = {
|
19
|
+
recipients: { to: 0, cc: 0, bcc: 0 },
|
20
|
+
attributes: { attachment: 0, campaign_id: 0, custom_option: 0, tag: 0 }
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
# Adds a specific type of recipient to the message object.
|
25
|
+
#
|
26
|
+
# WARNING: Setting 'h:reply-to' with add_recipient() is deprecated! Use 'reply_to' instead.
|
27
|
+
#
|
28
|
+
# @param [String] recipient_type The type of recipient. "to", "cc", "bcc" or "h:reply-to".
|
29
|
+
# @param [String] address The email address of the recipient to add to the message object.
|
30
|
+
# @param [Hash] variables A hash of the variables associated with the recipient. We recommend "first" and "last" at a minimum!
|
31
|
+
# @return [void]
|
32
|
+
def add_recipient(recipient_type, address, variables = nil)
|
33
|
+
if recipient_type == "h:reply-to"
|
34
|
+
warn 'DEPRECATION: "add_recipient("h:reply-to", ...)" is deprecated. Please use "reply_to" instead.'
|
35
|
+
return reply_to(address, variables)
|
36
|
+
end
|
37
|
+
|
38
|
+
if (@counters[:recipients][recipient_type] || 0) >= Mailgun::Chains::MAX_RECIPIENTS
|
39
|
+
fail Mailgun::ParameterError, 'Too many recipients added to message.', address
|
40
|
+
end
|
41
|
+
|
42
|
+
compiled_address = parse_address(address, variables)
|
43
|
+
set_multi_complex(recipient_type, compiled_address)
|
44
|
+
|
45
|
+
@counters[:recipients][recipient_type] += 1 if @counters[:recipients].key?(recipient_type)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Sets the from address for the message
|
49
|
+
#
|
50
|
+
# @param [String] address The address of the sender.
|
51
|
+
# @param [Hash] variables A hash of the variables associated with the recipient. We recommend "first" and "last" at a minimum!
|
52
|
+
# @return [void]
|
53
|
+
def from(address, vars = nil)
|
54
|
+
add_recipient(:from, address, vars)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Deprecated: please use 'from' instead.
|
58
|
+
def set_from_address(address, variables = nil)
|
59
|
+
warn 'DEPRECATION: "set_from_address" is deprecated. Please use "from" instead.'
|
60
|
+
from(address, variables)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Set the message's Reply-To address.
|
64
|
+
#
|
65
|
+
# Rationale: According to RFC, only one Reply-To address is allowed, so it
|
66
|
+
# is *okay* to bypass the set_multi_simple and set reply-to directly.
|
67
|
+
#
|
68
|
+
# @param [String] address The email address to provide as Reply-To.
|
69
|
+
# @param [Hash] variables A hash of variables associated with the recipient.
|
70
|
+
# @return [void]
|
71
|
+
def reply_to(address, variables = nil)
|
72
|
+
compiled_address = parse_address(address, variables)
|
73
|
+
header("reply-to", compiled_address)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Set a subject for the message object
|
77
|
+
#
|
78
|
+
# @param [String] subject The subject for the email.
|
79
|
+
# @return [void]
|
80
|
+
def subject(subj = nil)
|
81
|
+
set_multi_simple(:subject, subj)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Deprecated: Please use "subject" instead.
|
85
|
+
def set_subject(subj = nil)
|
86
|
+
warn 'DEPRECATION: "set_subject" is deprecated. Please use "subject" instead.'
|
87
|
+
subject(subj)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Set a text body for the message object
|
91
|
+
#
|
92
|
+
# @param [String] text_body The text body for the email.
|
93
|
+
# @return [void]
|
94
|
+
def body_text(text_body = nil)
|
95
|
+
set_multi_simple(:text, text_body)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Deprecated: Please use "body_text" instead.
|
99
|
+
def set_text_body(text_body = nil)
|
100
|
+
warn 'DEPRECATION: "set_text_body" is deprecated. Please use "body_text" instead.'
|
101
|
+
body_text(text_body)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Set a html body for the message object
|
105
|
+
#
|
106
|
+
# @param [String] html_body The html body for the email.
|
107
|
+
# @return [void]
|
108
|
+
def body_html(html_body = nil)
|
109
|
+
set_multi_simple(:html, html_body)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Deprecated: Please use "body_html" instead.
|
113
|
+
def set_html_body(html_body = nil)
|
114
|
+
warn 'DEPRECATION: "set_html_body" is deprecated. Please use "body_html" instead.'
|
115
|
+
body_html(html_body)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Adds a series of attachments, when called upon.
|
119
|
+
#
|
120
|
+
# @param [String|File] attachment A file object for attaching as an attachment.
|
121
|
+
# @param [String] filename The filename you wish the attachment to be.
|
122
|
+
# @return [void]
|
123
|
+
def add_attachment(attachment, filename = nil)
|
124
|
+
add_file(:attachment, attachment, filename)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Adds an inline image to the mesage object.
|
128
|
+
#
|
129
|
+
# @param [String|File] inline_image A file object for attaching an inline image.
|
130
|
+
# @param [String] filename The filename you wish the inline image to be.
|
131
|
+
# @return [void]
|
132
|
+
def add_inline_image(inline_image, filename = nil)
|
133
|
+
add_file(:inline, inline_image, filename)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Adds a List-Unsubscribe for the message header.
|
137
|
+
#
|
138
|
+
# @param [Array<String>] *variables Any number of url or mailto
|
139
|
+
# @return [void]
|
140
|
+
def list_unsubscribe(*variables)
|
141
|
+
set_single('h:List-Unsubscribe', variables.map { |var| "<#{var}>" }.join(','))
|
142
|
+
end
|
143
|
+
|
144
|
+
# Send a message in test mode. (The message won't really be sent to the recipient)
|
145
|
+
#
|
146
|
+
# @param [Boolean] mode The boolean or string value (will fix itself)
|
147
|
+
# @return [void]
|
148
|
+
def test_mode(mode)
|
149
|
+
set_multi_simple('o:testmode', bool_lookup(mode))
|
150
|
+
end
|
151
|
+
|
152
|
+
# Deprecated: 'set_test_mode' is depreciated. Please use 'test_mode' instead.
|
153
|
+
def set_test_mode(mode)
|
154
|
+
warn 'DEPRECATION: "set_test_mode" is deprecated. Please use "test_mode" instead.'
|
155
|
+
test_mode(mode)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Turn DKIM on or off per message
|
159
|
+
#
|
160
|
+
# @param [Boolean] mode The boolean or string value(will fix itself)
|
161
|
+
# @return [void]
|
162
|
+
def dkim(mode)
|
163
|
+
set_multi_simple('o:dkim', bool_lookup(mode))
|
164
|
+
end
|
165
|
+
|
166
|
+
# Deprecated: 'set_dkim' is deprecated. Please use 'dkim' instead.
|
167
|
+
def set_dkim(mode)
|
168
|
+
warn 'DEPRECATION: "set_dkim" is deprecated. Please use "dkim" instead.'
|
169
|
+
dkim(mode)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Add campaign IDs to message. Limit of 3 per message.
|
173
|
+
#
|
174
|
+
# @param [String] campaign_id A defined campaign ID to add to the message.
|
175
|
+
# @return [void]
|
176
|
+
def add_campaign_id(campaign_id)
|
177
|
+
fail(Mailgun::ParameterError, 'Too many campaigns added to message.', campaign_id) if @counters[:attributes][:campaign_id] >= Mailgun::Chains::MAX_CAMPAIGN_IDS
|
178
|
+
|
179
|
+
set_multi_complex('o:campaign', campaign_id)
|
180
|
+
@counters[:attributes][:campaign_id] += 1
|
181
|
+
end
|
182
|
+
|
183
|
+
# Add tags to message. Limit of 3 per message.
|
184
|
+
#
|
185
|
+
# @param [String] tag A defined campaign ID to add to the message.
|
186
|
+
# @return [void]
|
187
|
+
def add_tag(tag)
|
188
|
+
if @counters[:attributes][:tag] >= Mailgun::Chains::MAX_TAGS
|
189
|
+
fail Mailgun::ParameterError, 'Too many tags added to message.', tag
|
190
|
+
end
|
191
|
+
set_multi_complex('o:tag', tag)
|
192
|
+
@counters[:attributes][:tag] += 1
|
193
|
+
end
|
194
|
+
|
195
|
+
# Turn Open Tracking on and off, on a per message basis.
|
196
|
+
#
|
197
|
+
# @param [Boolean] tracking Boolean true or false.
|
198
|
+
# @return [void]
|
199
|
+
def track_opens(mode)
|
200
|
+
set_multi_simple('o:tracking-opens', bool_lookup(mode))
|
201
|
+
end
|
202
|
+
|
203
|
+
# Deprecated: 'set_open_tracking' is deprecated. Please use 'track_opens' instead.
|
204
|
+
def set_open_tracking(tracking)
|
205
|
+
warn 'DEPRECATION: "set_open_tracking" is deprecated. Please use "track_opens" instead.'
|
206
|
+
track_opens(tracking)
|
207
|
+
end
|
208
|
+
|
209
|
+
# Turn Click Tracking on and off, on a per message basis.
|
210
|
+
#
|
211
|
+
# @param [String] mode True, False, or HTML (for HTML only tracking)
|
212
|
+
# @return [void]
|
213
|
+
def track_clicks(mode)
|
214
|
+
set_multi_simple('o:tracking-clicks', bool_lookup(mode))
|
215
|
+
end
|
216
|
+
|
217
|
+
# Depreciated: 'set_click_tracking. is deprecated. Please use 'track_clicks' instead.
|
218
|
+
def set_click_tracking(tracking)
|
219
|
+
warn 'DEPRECATION: "set_click_tracking" is deprecated. Please use "track_clicks" instead.'
|
220
|
+
track_clicks(tracking)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Enable Delivery delay on message. Specify an RFC2822 date, and Mailgun
|
224
|
+
# will not deliver the message until that date/time. For conversion
|
225
|
+
# options, see Ruby "Time". Example: "October 25, 2013 10:00PM CST" will
|
226
|
+
# be converted to "Fri, 25 Oct 2013 22:00:00 -0600".
|
227
|
+
#
|
228
|
+
# @param [String] timestamp A date and time, including a timezone.
|
229
|
+
# @return [void]
|
230
|
+
def deliver_at(timestamp)
|
231
|
+
time_str = DateTime.parse(timestamp)
|
232
|
+
set_multi_simple('o:deliverytime', time_str.rfc2822)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Deprecated: 'set_delivery_time' is deprecated. Please use 'deliver_at' instead.
|
236
|
+
def set_delivery_time(timestamp)
|
237
|
+
warn 'DEPRECATION: "set_delivery_time" is deprecated. Please use "deliver_at" instead.'
|
238
|
+
deliver_at timestamp
|
239
|
+
end
|
240
|
+
|
241
|
+
# Add custom data to the message. The data should be either a hash or JSON
|
242
|
+
# encoded. The custom data will be added as a header to your message.
|
243
|
+
#
|
244
|
+
# @param [string] name A name for the custom data. (Ex. X-Mailgun-<Name of Data>: {})
|
245
|
+
# @param [Hash] data Either a hash or JSON string.
|
246
|
+
# @return [void]
|
247
|
+
def header(name, data)
|
248
|
+
fail(Mailgun::ParameterError, 'Header name for message must be specified') if name.to_s.empty?
|
249
|
+
begin
|
250
|
+
jsondata = make_json data
|
251
|
+
set_single("h:#{name}", jsondata)
|
252
|
+
rescue Mailgun::ParameterError
|
253
|
+
set_single("h:#{name}", data)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# Deprecated: 'set_custom_data' is deprecated. Please use 'header' instead.
|
258
|
+
def set_custom_data(name, data)
|
259
|
+
warn 'DEPRECATION: "set_custom_data" is deprecated. Please use "header" instead.'
|
260
|
+
header name, data
|
261
|
+
end
|
262
|
+
|
263
|
+
# Attaches custom JSON data to the message. See the following doc page for more info.
|
264
|
+
# https://documentation.mailgun.com/user_manual.html#attaching-data-to-messages
|
265
|
+
#
|
266
|
+
# @param [String] name A name for the custom variable block.
|
267
|
+
# @param [String|Hash] data Either a string or a hash. If it is not valid JSON or
|
268
|
+
# can not be converted to JSON, ParameterError will be raised.
|
269
|
+
# @return [void]
|
270
|
+
def variable(name, data)
|
271
|
+
fail(Mailgun::ParameterError, 'Variable name must be specified') if name.to_s.empty?
|
272
|
+
jsondata = make_json data
|
273
|
+
set_single("v:#{name}", jsondata)
|
274
|
+
end
|
275
|
+
|
276
|
+
# Add custom parameter to the message. A custom parameter is any parameter that
|
277
|
+
# is not yet supported by the SDK, but available at the API. Note: No validation
|
278
|
+
# is performed. Don't forget to prefix the parameter with o, h, or v.
|
279
|
+
#
|
280
|
+
# @param [string] name A name for the custom parameter.
|
281
|
+
# @param [string] data A string of data for the parameter.
|
282
|
+
# @return [void]
|
283
|
+
def add_custom_parameter(name, data)
|
284
|
+
set_multi_complex(name, data)
|
285
|
+
end
|
286
|
+
|
287
|
+
# Set the Message-Id header to a custom value. Don't forget to enclose the
|
288
|
+
# Message-Id in angle brackets, and ensure the @domain is present. Doesn't
|
289
|
+
# use simple or complex setters because it should not set value in an array.
|
290
|
+
#
|
291
|
+
# @param [string] data A string of data for the parameter. Passing nil or
|
292
|
+
# empty string will delete h:Message-Id key and value from @message hash.
|
293
|
+
# @return [void]
|
294
|
+
def message_id(data = nil)
|
295
|
+
key = 'h:Message-Id'
|
296
|
+
return @message.delete(key) if data.to_s.empty?
|
297
|
+
set_single(key, data)
|
298
|
+
end
|
299
|
+
|
300
|
+
# Deprecated: 'set_message_id' is deprecated. Use 'message_id' instead.
|
301
|
+
def set_message_id(data = nil)
|
302
|
+
warn 'DEPRECATION: "set_message_id" is deprecated. Please use "message_id" instead.'
|
303
|
+
message_id data
|
304
|
+
end
|
305
|
+
|
306
|
+
private
|
307
|
+
|
308
|
+
# Sets a single value in the message hash where "multidict" features are not needed.
|
309
|
+
# Does *not* permit duplicate params.
|
310
|
+
#
|
311
|
+
# @param [String] parameter The message object parameter name.
|
312
|
+
# @param [String] value The value of the parameter.
|
313
|
+
# @return [void]
|
314
|
+
def set_single(parameter, value)
|
315
|
+
@message[parameter] = value ? value : ''
|
316
|
+
end
|
317
|
+
|
318
|
+
# Sets values within the multidict, however, prevents
|
319
|
+
# duplicate values for keys.
|
320
|
+
#
|
321
|
+
# @param [String] parameter The message object parameter name.
|
322
|
+
# @param [String] value The value of the parameter.
|
323
|
+
# @return [void]
|
324
|
+
def set_multi_simple(parameter, value)
|
325
|
+
@message[parameter] = value ? [value] : ['']
|
326
|
+
end
|
327
|
+
|
328
|
+
# Sets values within the multidict, however, allows
|
329
|
+
# duplicate values for keys.
|
330
|
+
#
|
331
|
+
# @param [String] parameter The message object parameter name.
|
332
|
+
# @param [String] value The value of the parameter.
|
333
|
+
# @return [void]
|
334
|
+
def set_multi_complex(parameter, value)
|
335
|
+
@message[parameter] << (value || '')
|
336
|
+
end
|
337
|
+
|
338
|
+
# Converts boolean type to string
|
339
|
+
#
|
340
|
+
# @param [String] value The item to convert
|
341
|
+
# @return [void]
|
342
|
+
def bool_lookup(value)
|
343
|
+
return 'yes' if %w(true yes yep).include? value.to_s.downcase
|
344
|
+
return 'no' if %w(false no nope).include? value.to_s.downcase
|
345
|
+
value
|
346
|
+
end
|
347
|
+
|
348
|
+
# Validates whether the input is JSON.
|
349
|
+
#
|
350
|
+
# @param [String] json_ The suspected JSON string.
|
351
|
+
# @return [void]
|
352
|
+
def valid_json?(json_)
|
353
|
+
JSON.parse(json_)
|
354
|
+
return true
|
355
|
+
rescue JSON::ParserError
|
356
|
+
false
|
357
|
+
end
|
358
|
+
|
359
|
+
# Private: given an object attempt to make it into JSON
|
360
|
+
#
|
361
|
+
# obj - an object. Hopefully a JSON string or Hash
|
362
|
+
#
|
363
|
+
# Returns a JSON object or raises ParameterError
|
364
|
+
def make_json(obj)
|
365
|
+
return JSON.parse(obj).to_json if obj.is_a?(String)
|
366
|
+
return obj.to_json if obj.is_a?(Hash)
|
367
|
+
return JSON.generate(obj).to_json
|
368
|
+
rescue
|
369
|
+
raise Mailgun::ParameterError, 'Provided data could not be made into JSON. Try a JSON string or Hash.', obj
|
370
|
+
end
|
371
|
+
|
372
|
+
# Parses the address and gracefully handles any
|
373
|
+
# missing parameters. The result should be something like:
|
374
|
+
# "'First Last' <person@domain.com>"
|
375
|
+
#
|
376
|
+
# @param [String] address The email address to parse.
|
377
|
+
# @param [Hash] variables A list of recipient variables.
|
378
|
+
# @return [void]
|
379
|
+
def parse_address(address, vars)
|
380
|
+
return address unless vars.is_a? Hash
|
381
|
+
fail(Mailgun::ParameterError, 'Email address not specified') unless address.is_a? String
|
382
|
+
|
383
|
+
full_name = "#{vars['first']} #{vars['last']}".strip
|
384
|
+
|
385
|
+
return "'#{full_name}' <#{address}>" if defined?(full_name)
|
386
|
+
address
|
387
|
+
end
|
388
|
+
|
389
|
+
# Private: Adds a file to the message.
|
390
|
+
#
|
391
|
+
# @param [Symbol] disposition The type of file: :attachment or :inline
|
392
|
+
# @param [String|File] attachment A file object for attaching as an attachment.
|
393
|
+
# @param [String] filename The filename you wish the attachment to be.
|
394
|
+
# @return [void]
|
395
|
+
#
|
396
|
+
# Returns nothing
|
397
|
+
def add_file(disposition, filedata, filename)
|
398
|
+
attachment = File.open(filedata, 'r') if filedata.is_a?(String)
|
399
|
+
attachment = filedata.dup unless attachment
|
400
|
+
|
401
|
+
fail(Mailgun::ParameterError,
|
402
|
+
'Unable to access attachment file object.'
|
403
|
+
) unless attachment.respond_to?(:read)
|
404
|
+
|
405
|
+
unless filename.nil?
|
406
|
+
attachment.instance_variable_set :@original_filename, filename
|
407
|
+
attachment.instance_eval 'def original_filename; @original_filename; end'
|
408
|
+
end
|
409
|
+
set_multi_complex(disposition, attachment)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
end
|