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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.rubocop.yml +8 -0
  4. data/.rubocop_todo.yml +22 -0
  5. data/.ruby-env.yml.example +12 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +24 -0
  8. data/Gemfile +6 -0
  9. data/LICENSE +191 -0
  10. data/README.md +241 -0
  11. data/Rakefile +35 -0
  12. data/docs/Domains.md +54 -0
  13. data/docs/Events.md +46 -0
  14. data/docs/MessageBuilder.md +105 -0
  15. data/docs/Messages.md +107 -0
  16. data/docs/OptInHandler.md +103 -0
  17. data/docs/Snippets.md +526 -0
  18. data/docs/Suppressions.md +82 -0
  19. data/docs/Webhooks.md +40 -0
  20. data/lib/mailgun-ruby.rb +2 -0
  21. data/lib/mailgun.rb +39 -0
  22. data/lib/mailgun/address.rb +45 -0
  23. data/lib/mailgun/chains.rb +16 -0
  24. data/lib/mailgun/client.rb +199 -0
  25. data/lib/mailgun/domains/domains.rb +84 -0
  26. data/lib/mailgun/events/events.rb +120 -0
  27. data/lib/mailgun/exceptions/exceptions.rb +65 -0
  28. data/lib/mailgun/lists/opt_in_handler.rb +58 -0
  29. data/lib/mailgun/messages/batch_message.rb +125 -0
  30. data/lib/mailgun/messages/message_builder.rb +413 -0
  31. data/lib/mailgun/response.rb +62 -0
  32. data/lib/mailgun/suppressions.rb +270 -0
  33. data/lib/mailgun/version.rb +4 -0
  34. data/lib/mailgun/webhooks/webhooks.rb +101 -0
  35. data/lib/railgun.rb +8 -0
  36. data/lib/railgun/attachment.rb +56 -0
  37. data/lib/railgun/errors.rb +27 -0
  38. data/lib/railgun/mailer.rb +161 -0
  39. data/lib/railgun/message.rb +17 -0
  40. data/lib/railgun/railtie.rb +9 -0
  41. data/mailgun.gemspec +37 -0
  42. data/spec/integration/bounces_spec.rb +44 -0
  43. data/spec/integration/campaign_spec.rb +60 -0
  44. data/spec/integration/complaints_spec.rb +38 -0
  45. data/spec/integration/domains_spec.rb +39 -0
  46. data/spec/integration/email_validation_spec.rb +57 -0
  47. data/spec/integration/events_spec.rb +28 -0
  48. data/spec/integration/list_members_spec.rb +63 -0
  49. data/spec/integration/list_spec.rb +58 -0
  50. data/spec/integration/mailgun_spec.rb +121 -0
  51. data/spec/integration/messages/sample_data/mime.txt +38 -0
  52. data/spec/integration/routes_spec.rb +74 -0
  53. data/spec/integration/stats_spec.rb +15 -0
  54. data/spec/integration/suppressions_spec.rb +126 -0
  55. data/spec/integration/unsubscribes_spec.rb +42 -0
  56. data/spec/integration/webhook_spec.rb +54 -0
  57. data/spec/spec_helper.rb +45 -0
  58. data/spec/unit/connection/test_client.rb +99 -0
  59. data/spec/unit/events/events_spec.rb +50 -0
  60. data/spec/unit/lists/opt_in_handler_spec.rb +24 -0
  61. data/spec/unit/mailgun_spec.rb +127 -0
  62. data/spec/unit/messages/batch_message_spec.rb +131 -0
  63. data/spec/unit/messages/message_builder_spec.rb +584 -0
  64. data/spec/unit/messages/sample_data/mailgun_icon.png +0 -0
  65. data/spec/unit/messages/sample_data/mime.txt +38 -0
  66. data/spec/unit/messages/sample_data/rackspace_logo.jpg +0 -0
  67. data/vcr_cassettes/bounces.yml +175 -0
  68. data/vcr_cassettes/complaints.yml +175 -0
  69. data/vcr_cassettes/domains.todo.yml +42 -0
  70. data/vcr_cassettes/domains.yml +360 -0
  71. data/vcr_cassettes/email_validation.yml +167 -0
  72. data/vcr_cassettes/events.yml +108 -0
  73. data/vcr_cassettes/exceptions.yml +45 -0
  74. data/vcr_cassettes/list_members.yml +320 -0
  75. data/vcr_cassettes/mailing_list.todo.yml +43 -0
  76. data/vcr_cassettes/mailing_list.yml +390 -0
  77. data/vcr_cassettes/routes.yml +359 -0
  78. data/vcr_cassettes/send_message.yml +107 -0
  79. data/vcr_cassettes/stats.yml +44 -0
  80. data/vcr_cassettes/suppressions.yml +676 -0
  81. data/vcr_cassettes/unsubscribes.yml +191 -0
  82. data/vcr_cassettes/webhooks.yml +276 -0
  83. 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