postal-ruby 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: