wj-mailgun-ruby 1.1.7
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 +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
|