appmail 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cf42054c6e347a5c7cad150857aa334eab7e5ea6
4
+ data.tar.gz: 6a1e0e40ed2c04a67232875132d9d2dddb4ad7ee
5
+ SHA512:
6
+ metadata.gz: b7e91f5b55dff971e14a56a146684d23666737209459d40c01da057f985e2b1bc5903667357ae4eaea8b8cd489ebe8d87ff526f745748c673af9492f8ba40386
7
+ data.tar.gz: 9d90c3d37b84bfe5e8585d9a72607ab214b137765d2afb952d16dc144102d1562f5b9da7e2cd18fcacc171aaccd476d1162a2d84e99ead6e27c5afb8dad37970
@@ -0,0 +1,18 @@
1
+ require 'app_mail/client'
2
+ require 'app_mail/config'
3
+
4
+ module AppMail
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
+ AppMail::Client.instance.send_message(&block)
16
+ end
17
+
18
+ end
@@ -0,0 +1,31 @@
1
+ require 'base64'
2
+
3
+ module AppMail
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,51 @@
1
+ require 'moonrope_client'
2
+ require 'app_mail/message_scope'
3
+ require 'app_mail/send_message'
4
+
5
+ module AppMail
6
+ class Client
7
+
8
+ #
9
+ # Create and cache a global instance of client based on the environment variables
10
+ # which can be provided. In 90% of cases, AppMail will be accessed through this.
11
+ #
12
+ def self.instance
13
+ @instance ||= Client.new(AppMail.config.host, AppMail.config.server_key)
14
+ end
15
+
16
+ #
17
+ # Initialize a new client with the host and API key
18
+ #
19
+ def initialize(host, server_key)
20
+ @host = host
21
+ @server_key = server_key
22
+ end
23
+
24
+ #
25
+ # Provide a scope to access messages
26
+ #
27
+ def messages
28
+ MessageScope.new(self)
29
+ end
30
+
31
+ #
32
+ # Send a message
33
+ #
34
+ def send_message(&block)
35
+ message = SendMessage.new(self)
36
+ block.call(message)
37
+ message.send!
38
+ end
39
+
40
+ #
41
+ # Return the backend moonrope instance for this client
42
+ #
43
+ def moonrope
44
+ @moonrope ||= begin
45
+ headers= {'X-Server-API-Key' => @server_key}
46
+ MoonropeClient::Connection.new(@host, :headers => headers, :ssl => true)
47
+ end
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,15 @@
1
+ module AppMail
2
+ class Config
3
+
4
+ def host
5
+ @host || ENV['APPMAIL_HOST'] || 'api.appmail.io'
6
+ end
7
+ attr_writer :host
8
+
9
+ def server_key
10
+ @server_key || ENV['APPMAIL_KEY'] || raise(Error, "Server key has not been configured. Set it using the `AppMail.configure` block or use `APPMAIL_KEY` environment variable.")
11
+ end
12
+ attr_writer :server_key
13
+
14
+ end
15
+ end
@@ -0,0 +1,47 @@
1
+ module AppMail
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 AppMail
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 'app_mail/error'
2
+ require 'app_mail/header_set'
3
+ require 'app_mail/attachment'
4
+
5
+ module AppMail
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
+ AppMail::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 AppMail::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 'app_mail/message'
2
+
3
+ module AppMail
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,77 @@
1
+ require 'base64'
2
+ require 'app_mail/send_result'
3
+
4
+ module AppMail
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 to(*addresses)
28
+ @attributes[:to] ||= []
29
+ @attributes[:to] += addresses
30
+ end
31
+
32
+ def cc(*addresses)
33
+ @attributes[:cc] ||= []
34
+ @attributes[:cc] += addresses
35
+ end
36
+
37
+ def bcc(*addresses)
38
+ @attributes[:bcc] ||= []
39
+ @attributes[:bcc] += addresses
40
+ end
41
+
42
+ def subject(subject)
43
+ @attributes[:subject] = subject
44
+ end
45
+
46
+ def tag(tag)
47
+ @attributes[:tag] = subject
48
+ end
49
+
50
+ def reply_to(reply_to)
51
+ @attributes[:reply_to] = subject
52
+ end
53
+
54
+ def plain_body(content)
55
+ @attributes[:plain_body] = content
56
+ end
57
+
58
+ def html_body(content)
59
+ @attributes[:html_body] = content
60
+ end
61
+
62
+ def header(key, value)
63
+ @attributes[:headers] ||= {}
64
+ @attributes[:headers][key.to_s] = value
65
+ end
66
+
67
+ def attach(filename, content_type, data)
68
+ @attributes[:attachments] ||= []
69
+ @attributes[:attachments] << {
70
+ :name => filename,
71
+ :content_type => content_type,
72
+ :data => Base64.encode64(data)
73
+ }
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,30 @@
1
+ module AppMail
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 AppMail
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1 @@
1
+ require 'app_mail'
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: appmail
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Adam Cooke
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-10-29 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 AppMail E-Mail Platform
34
+ email:
35
+ - me@adamcooke.io
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - lib/app_mail.rb
41
+ - lib/app_mail/attachment.rb
42
+ - lib/app_mail/client.rb
43
+ - lib/app_mail/config.rb
44
+ - lib/app_mail/error.rb
45
+ - lib/app_mail/header_set.rb
46
+ - lib/app_mail/message.rb
47
+ - lib/app_mail/message_scope.rb
48
+ - lib/app_mail/send_message.rb
49
+ - lib/app_mail/send_result.rb
50
+ - lib/app_mail/version.rb
51
+ - lib/appmail.rb
52
+ homepage: https://appmail.io
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 AppMail E-Mail Platform
76
+ test_files: []