postal-ruby 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: decfbd216e5711fdb213bd6896c656f608e252b7
4
+ data.tar.gz: f7696e1e19fbca0f46a6f9ba0475b5b7f124decb
5
+ SHA512:
6
+ metadata.gz: 5d2a13891fdd768c6fa07d819fb2fe702e599888aebfccac0a47e37171b0bafae9cf4d46afdab61694dbefe76340062724d8f3c16d7ed3c1df9555787ea6f7d3
7
+ data.tar.gz: 9583d073dbcaee6ae0a1ff1f8d9203b3e325645c66e9b986de38d54b8ee0f8ae4c0e25282a44f19b9babc4031e508271daaba44838cd0e25b27cea76a75439e7
data/lib/postal.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'postal/client'
2
+ require 'postal/config'
3
+
4
+ module Postal
5
+
6
+ def self.config
7
+ @config ||= Config.new
8
+ end
9
+
10
+ def self.configure(&block)
11
+ block.call(config)
12
+ end
13
+
14
+ def self.send_message(&block)
15
+ Postal::Client.instance.send_message(&block)
16
+ end
17
+
18
+ def self.send_raw_message(&block)
19
+ Postal::Client.instance.send_raw_message(&block)
20
+ end
21
+
22
+ end
@@ -0,0 +1,31 @@
1
+ require 'base64'
2
+
3
+ module Postal
4
+ class Attachment
5
+
6
+ def initialize(attributes)
7
+ @attributes = attributes
8
+ end
9
+
10
+ def filename
11
+ @attributes['filename']
12
+ end
13
+
14
+ def content_type
15
+ @attributes['content_type']
16
+ end
17
+
18
+ def size
19
+ @attributes['size']
20
+ end
21
+
22
+ def hash
23
+ @attributes['hash']
24
+ end
25
+
26
+ def data
27
+ @data ||= Base64.decode64(@attributes['data'])
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,61 @@
1
+ require 'moonrope_client'
2
+ require 'postal/message_scope'
3
+ require 'postal/send_message'
4
+ require 'postal/send_raw_message'
5
+
6
+ module Postal
7
+ class Client
8
+
9
+ #
10
+ # Create and cache a global instance of client based on the environment variables
11
+ # which can be provided. In 90% of cases, Postal will be accessed through this.
12
+ #
13
+ def self.instance
14
+ @instance ||= Client.new(Postal.config.host, Postal.config.server_key)
15
+ end
16
+
17
+ #
18
+ # Initialize a new client with the host and API key
19
+ #
20
+ def initialize(host, server_key)
21
+ @host = host
22
+ @server_key = server_key
23
+ end
24
+
25
+ #
26
+ # Provide a scope to access messages
27
+ #
28
+ def messages
29
+ MessageScope.new(self)
30
+ end
31
+
32
+ #
33
+ # Send a message
34
+ #
35
+ def send_message(&block)
36
+ message = SendMessage.new(self)
37
+ block.call(message)
38
+ message.send!
39
+ end
40
+
41
+ #
42
+ # Send a raw message
43
+ #
44
+ def send_raw_message(&block)
45
+ message = SendRawMessage.new(self)
46
+ block.call(message)
47
+ message.send!
48
+ end
49
+
50
+ #
51
+ # Return the backend moonrope instance for this client
52
+ #
53
+ def moonrope
54
+ @moonrope ||= begin
55
+ headers= {'X-Server-API-Key' => @server_key}
56
+ MoonropeClient::Connection.new(@host, :headers => headers, :ssl => true)
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,15 @@
1
+ module Postal
2
+ class Config
3
+
4
+ def host
5
+ @host || ENV['POSTAL_HOST'] || raise(Error, "Host has not been configured. Set it using the `Postal.configure` block or use `POSTAL_HOST` environment variable.")
6
+ end
7
+ attr_writer :host
8
+
9
+ def server_key
10
+ @server_key || ENV['POSTAL_KEY'] || raise(Error, "Server key has not been configured. Set it using the `Postal.configure` block or use `POSTAL_KEY` environment variable.")
11
+ end
12
+ attr_writer :server_key
13
+
14
+ end
15
+ end
@@ -0,0 +1,47 @@
1
+ module Postal
2
+
3
+ #
4
+ # A generic error that all errors will inherit from
5
+ #
6
+ class Error < StandardError
7
+ end
8
+
9
+ #
10
+ # Raised when a message cannot be found by its ID
11
+ #
12
+ class MessageNotFound < Error
13
+ def initialize(id)
14
+ @id = id
15
+ end
16
+
17
+ def message
18
+ "No message found matching ID '#{@id}'"
19
+ end
20
+
21
+ def to_s
22
+ message
23
+ end
24
+ end
25
+
26
+ #
27
+ # Raised when a message cannot be found by its ID
28
+ #
29
+ class SendError < Error
30
+ def initialize(code, error_message)
31
+ @code = code
32
+ @error_message = error_message
33
+ end
34
+
35
+ attr_reader :code
36
+ attr_reader :error_message
37
+
38
+ def message
39
+ "[#{@code}] #{@error_message}"
40
+ end
41
+
42
+ def to_s
43
+ message
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,21 @@
1
+ module Postal
2
+ class HeaderSet
3
+
4
+ def initialize(headers)
5
+ @headers = headers
6
+ end
7
+
8
+ def [](name)
9
+ @headers[name.to_s.downcase]
10
+ end
11
+
12
+ def has_key?(key)
13
+ @headers.has_key?(name.to_s.downcase)
14
+ end
15
+
16
+ def method_missing(*args, &block)
17
+ @headers.send(*args, &block)
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,156 @@
1
+ require 'postal/error'
2
+ require 'postal/header_set'
3
+ require 'postal/attachment'
4
+
5
+ module Postal
6
+ class Message
7
+
8
+ #
9
+ # Find a specific messsage with the given scope
10
+ #
11
+ def self.find_with_scope(scope, id)
12
+ api = scope.client.moonrope.messages.message(:id => id.to_i, :_expansions => scope.expansions)
13
+ if api.success?
14
+ Message.new(scope.client, api.data)
15
+ elsif api.status == 'error' && api.data['code'] == 'MessageNotFound'
16
+ raise MessageNotFound.new(id)
17
+ else
18
+ raise Error, "Couldn't load message from API (#{api.data})"
19
+ end
20
+ end
21
+
22
+ #
23
+ # If methods are called directly on the Message class, we likely want to see if we can
24
+ # run them through the global client message scope.
25
+ #
26
+ def self.method_missing(name, *args, &block)
27
+ if MessageScope.instance_methods(false).include?(name)
28
+ Postal::Client.instance.messages.send(name, *args, &block)
29
+ else
30
+ super
31
+ end
32
+ end
33
+
34
+ #
35
+ # Initialize a new message object with the client and a set of initial attributes.
36
+ #
37
+ def initialize(client, attributes)
38
+ @client = client
39
+ @attributes = attributes
40
+ end
41
+
42
+ #
43
+ # Return the message ID
44
+ #
45
+ def id
46
+ @attributes['id']
47
+ end
48
+
49
+ #
50
+ # Return the message token
51
+ #
52
+ def token
53
+ @attributes['token']
54
+ end
55
+
56
+ #
57
+ # Set a has of all the attributes from the API that should be exposed through
58
+ # the Message class.
59
+ #
60
+ ATTRIBUTES = {
61
+ :status => [:status, :status],
62
+ :last_delivery_attempt => [:status, :last_delivery_attempt, :timestamp],
63
+ :held? => [:status, :held, :boolean],
64
+ :hold_expiry => [:status, :hold_expiry, :timestamp],
65
+ :rcpt_to => [:details, :rcpt_to],
66
+ :mail_from => [:details, :mail_from],
67
+ :subject => [:details, :subject],
68
+ :message_id => [:details, :message_id],
69
+ :timestamp => [:details, :timestamp, :timestamp],
70
+ :direction => [:details, :direction],
71
+ :size => [:details, :size],
72
+ :bounce? => [:details, :bounce, :boolean],
73
+ :bounce_for_id => [:details, :bounce],
74
+ :tag => [:details, :tag],
75
+ :received_with_ssl? => [:details, :received_with_ssl, :boolean],
76
+ :inspected? => [:inspection, :inspected, :boolean],
77
+ :spam? => [:inspection, :spam, :boolean],
78
+ :spam_score => [:inspection, :spam_score],
79
+ :threat? => [:inspection, :thret, :boolean],
80
+ :threat_details => [:inspection, :threat_details],
81
+ :plain_body => [:plain_body],
82
+ :html_body => [:html_body],
83
+ }
84
+
85
+ #
86
+ # Catch calls to any of the default attributes for a message and return the
87
+ # data however we'd like it
88
+ #
89
+ def method_missing(name, *args, &block)
90
+ if mapping = ATTRIBUTES[name.to_sym]
91
+ expansion, attribute, type = mapping
92
+ value = from_expansion(expansion, attribute)
93
+ case type
94
+ when :timestamp
95
+ value ? Time.at(value) : nil
96
+ when :boolean
97
+ value == 1
98
+ else
99
+ value
100
+ end
101
+ else
102
+ super
103
+ end
104
+ end
105
+
106
+ #
107
+ # Return a set of headers which can be queried like a hash however looking up
108
+ # values using [] will be case-insensitive.
109
+ #
110
+ def headers
111
+ @headers ||= HeaderSet.new(from_expansion(:headers))
112
+ end
113
+
114
+ #
115
+ # Return an array of attachment objects
116
+ #
117
+ def attachments
118
+ @attachments ||= from_expansion(:attachments).map do |a|
119
+ Attachment.new(a)
120
+ end
121
+ end
122
+
123
+ #
124
+ # Return the full raw message
125
+ #
126
+ def raw_message
127
+ @raw_message ||= Base64.decode64(from_expansion(:raw_message))
128
+ end
129
+
130
+ private
131
+
132
+ def from_expansion(expansion, attribute = nil, loaded = false)
133
+ if @attributes.has_key?(expansion.to_s) || loaded
134
+ attribute ? @attributes[expansion.to_s][attribute.to_s] : @attributes[expansion.to_s]
135
+ else
136
+ load_expansions(expansion)
137
+ from_expansion(expansion, attribute, true)
138
+ end
139
+ end
140
+
141
+ def load_expansions(*names)
142
+ puts "\e[31mLoading expansion #{names}\e[0m"
143
+ api = @client.moonrope.messages.message(:id => self.id, :_expansions => names)
144
+ if api.success?
145
+ names.each do |expansion_name|
146
+ if api.data.has_key?(expansion_name.to_s)
147
+ @attributes[expansion_name.to_s] = api.data[expansion_name.to_s]
148
+ end
149
+ end
150
+ else
151
+ raise Postal::Error, "Couldn't load expansion data (#{names.join(', ')}) for message ID '#{self.id}'"
152
+ end
153
+ end
154
+
155
+ end
156
+ end
@@ -0,0 +1,40 @@
1
+ require 'postal/message'
2
+
3
+ module Postal
4
+ class MessageScope
5
+
6
+ attr_reader :client
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ @includes = []
11
+ end
12
+
13
+ #
14
+ # Add includes to the scope
15
+ #
16
+ def includes(*includes)
17
+ @includes.push(*includes)
18
+ self
19
+ end
20
+
21
+ #
22
+ # Return the current includes
23
+ #
24
+ def expansions
25
+ if @includes.include?(:all)
26
+ true
27
+ else
28
+ @includes.map(&:to_s)
29
+ end
30
+ end
31
+
32
+ #
33
+ # Find a given message by its ID
34
+ #
35
+ def find_by_id(id)
36
+ Message.find_with_scope(self, id)
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,81 @@
1
+ require 'base64'
2
+ require 'postal/send_result'
3
+
4
+ module Postal
5
+ class SendMessage
6
+
7
+ def initialize(client)
8
+ @client = client
9
+ @attributes = {}
10
+ end
11
+
12
+ def send!
13
+ api = @client.moonrope.request(:send, :message, @attributes)
14
+ if api.success?
15
+ SendResult.new(@client, api.data)
16
+ elsif api.status == 'error'
17
+ raise SendError.new(api.data['code'], api.data['message'])
18
+ else
19
+ raise Error, "Couldn't send message"
20
+ end
21
+ end
22
+
23
+ def from(address)
24
+ @attributes[:from] = address
25
+ end
26
+
27
+ def sender(address)
28
+ @attributes[:sender] = address
29
+ end
30
+
31
+ def to(*addresses)
32
+ @attributes[:to] ||= []
33
+ @attributes[:to] += addresses
34
+ end
35
+
36
+ def cc(*addresses)
37
+ @attributes[:cc] ||= []
38
+ @attributes[:cc] += addresses
39
+ end
40
+
41
+ def bcc(*addresses)
42
+ @attributes[:bcc] ||= []
43
+ @attributes[:bcc] += addresses
44
+ end
45
+
46
+ def subject(subject)
47
+ @attributes[:subject] = subject
48
+ end
49
+
50
+ def tag(tag)
51
+ @attributes[:tag] = subject
52
+ end
53
+
54
+ def reply_to(reply_to)
55
+ @attributes[:reply_to] = subject
56
+ end
57
+
58
+ def plain_body(content)
59
+ @attributes[:plain_body] = content
60
+ end
61
+
62
+ def html_body(content)
63
+ @attributes[:html_body] = content
64
+ end
65
+
66
+ def header(key, value)
67
+ @attributes[:headers] ||= {}
68
+ @attributes[:headers][key.to_s] = value
69
+ end
70
+
71
+ def attach(filename, content_type, data)
72
+ @attributes[:attachments] ||= []
73
+ @attributes[:attachments] << {
74
+ :name => filename,
75
+ :content_type => content_type,
76
+ :data => Base64.encode64(data)
77
+ }
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,37 @@
1
+ require 'base64'
2
+ require 'postal/send_result'
3
+
4
+ module Postal
5
+ class SendRawMessage
6
+
7
+ def initialize(client)
8
+ @client = client
9
+ @attributes = {}
10
+ end
11
+
12
+ def send!
13
+ api = @client.moonrope.request(:send, :raw, @attributes)
14
+ if api.success?
15
+ SendResult.new(@client, api.data)
16
+ elsif api.status == 'error'
17
+ raise SendError.new(api.data['code'], api.data['message'])
18
+ else
19
+ raise Error, "Couldn't send message"
20
+ end
21
+ end
22
+
23
+ def mail_from(address)
24
+ @attributes[:mail_from] = address
25
+ end
26
+
27
+ def rcpt_to(*addresses)
28
+ @attributes[:rcpt_to] ||= []
29
+ @attributes[:rcpt_to] += addresses
30
+ end
31
+
32
+ def data(data)
33
+ @attributes[:data] = Base64.encode64(data)
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,30 @@
1
+ module Postal
2
+ class SendResult
3
+
4
+ def initialize(client, result)
5
+ @client = client
6
+ @result = result
7
+ end
8
+
9
+ def message_id
10
+ @result['message_id']
11
+ end
12
+
13
+ def recipients
14
+ @recipients ||= begin
15
+ @result['messages'].each_with_object({}) do |(recipient, message_details), hash|
16
+ hash[recipient.to_s.downcase] = Message.new(@client, message_details)
17
+ end
18
+ end
19
+ end
20
+
21
+ def [](recipient)
22
+ recipients[recipient.to_s.downcase]
23
+ end
24
+
25
+ def size
26
+ recipients.size
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module Postal
2
+ VERSION = '1.0.0'
3
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: postal-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Adam Cooke
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-04-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: moonrope-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.2
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '1.1'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.2
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.1'
33
+ description: Ruby library for the Postal e-mail platform
34
+ email:
35
+ - me@adamcooke.io
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - lib/postal.rb
41
+ - lib/postal/attachment.rb
42
+ - lib/postal/client.rb
43
+ - lib/postal/config.rb
44
+ - lib/postal/error.rb
45
+ - lib/postal/header_set.rb
46
+ - lib/postal/message.rb
47
+ - lib/postal/message_scope.rb
48
+ - lib/postal/send_message.rb
49
+ - lib/postal/send_raw_message.rb
50
+ - lib/postal/send_result.rb
51
+ - lib/postal/version.rb
52
+ homepage: https://github.com/atech/postal
53
+ licenses:
54
+ - MIT
55
+ metadata: {}
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubyforge_project:
72
+ rubygems_version: 2.5.1
73
+ signing_key:
74
+ specification_version: 4
75
+ summary: Ruby library for the Postal e-mail platform
76
+ test_files: []
77
+ has_rdoc: