mailflow 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f617724f63d55f15c40dd14b1b7156f29d7d0dfccf267c11cf9f7f8a5191424a
4
+ data.tar.gz: efe48e465aeb2b3c6f41989410277a814b0c34691e2f4725c2f4470ea746a1af
5
+ SHA512:
6
+ metadata.gz: f376c6309c998b0d778b63aacc595b51cf6985d4f21841ab11b15cdc6f1bd793ed3ba0b2f5a60c9e61f1e90397b40eefabe05cd8dd3b8fde5c6574cb797e5f83
7
+ data.tar.gz: f653456b8f272d9255723cbf8b7e8d09e0894c80e56db4eb4df18190da2a1fe5716de889b70573eddd807a9682774a510135f50faa204b3e455d5b94c842a851
@@ -0,0 +1,15 @@
1
+ require 'base64'
2
+
3
+ module Mailflow
4
+ class Attachment
5
+ attr_reader :filename, :content_type, :size, :hash, :data
6
+
7
+ def initialize(attributes)
8
+ @filename = attributes['filename']
9
+ @content_type = attributes['content_type']
10
+ @size = attributes['size']
11
+ @hash = attributes['hash']
12
+ @data = Base64.decode64(attributes['data'])
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,39 @@
1
+ require 'moonrope_client'
2
+ require 'mailflow/message_scope'
3
+ require 'mailflow/send_message'
4
+ require 'mailflow/send_raw_message'
5
+
6
+ module Mailflow
7
+ class Client
8
+ attr_reader :host, :server_key
9
+
10
+ def self.instance
11
+ @instance ||= new(Mailflow.config.host, Mailflow.config.server_key)
12
+ end
13
+
14
+ def initialize(host, server_key)
15
+ @host = host
16
+ @server_key = server_key
17
+ end
18
+
19
+ def messages
20
+ @messages ||= MessageScope.new(self)
21
+ end
22
+
23
+ def send_message(&block)
24
+ send_message = SendMessage.new(self)
25
+ block.call(send_message)
26
+ send_message.send!
27
+ end
28
+
29
+ def send_raw_message(&block)
30
+ send_raw_message = SendRawMessage.new(self)
31
+ block.call(send_raw_message)
32
+ send_raw_message.send!
33
+ end
34
+
35
+ def moonrope
36
+ @moonrope ||= MoonropeClient::Connection.new(@host, headers: { 'X-Server-API-Key' => @server_key }, ssl: true)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,18 @@
1
+ module Mailflow
2
+ class Config
3
+ attr_accessor :host, :server_key
4
+
5
+ def initialize
6
+ @host = ENV['MAILFLOW_HOST']
7
+ @server_key = ENV['MAILFLOW_KEY']
8
+ end
9
+
10
+ def host
11
+ @host ||= raise(Error, "The host has not been configured. Please set it using the `Mailflow.configure` block or utilize the `MAILFLOW_HOST` environment variable.")
12
+ end
13
+
14
+ def server_key
15
+ @server_key ||= raise(Error, "The server key has not been configured. Please set it using the `Mailflow.configure` block or make use of the `MAILFLOW_KEY` environment variable.")
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,30 @@
1
+ module Mailflow
2
+ class Error < StandardError; end
3
+
4
+ class MessageNotFound < Error
5
+ def initialize(id)
6
+ @id = id
7
+ end
8
+
9
+ def message
10
+ "No message was found that matches the provided ID. '#{@id}'"
11
+ end
12
+
13
+ alias to_s message
14
+ end
15
+
16
+ class SendError < Error
17
+ def initialize(code, error_message)
18
+ @code = code
19
+ @error_message = error_message
20
+ end
21
+
22
+ attr_reader :code, :error_message
23
+
24
+ def message
25
+ "[#{@code}] #{@error_message}"
26
+ end
27
+
28
+ alias to_s message
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ module Mailflow
2
+ class HeaderSet
3
+ attr_reader :headers
4
+
5
+ def initialize(headers)
6
+ @headers = headers.transform_keys(&:to_s).transform_keys(&:downcase)
7
+ end
8
+
9
+ def [](name)
10
+ headers[name.to_s.downcase]
11
+ end
12
+
13
+ def has_key?(key)
14
+ headers.has_key?(key.to_s.downcase)
15
+ end
16
+
17
+ def method_missing(*args, &block)
18
+ headers.send(*args, &block)
19
+ end
20
+
21
+ def respond_to_missing?(method_name, include_private = false)
22
+ headers.respond_to?(method_name) || super
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,104 @@
1
+ require 'mailflow/error'
2
+ require 'mailflow/header_set'
3
+ require 'mailflow/attachment'
4
+
5
+ module Mailflow
6
+ class Message
7
+ attr_reader :id, :token, :headers, :attachments, :raw_message
8
+
9
+ def self.find_with_scope(scope, id)
10
+ api = scope.client.moonrope.messages.message(id: id.to_i, _expansions: scope.expansions)
11
+ if api.success?
12
+ Message.new(scope.client, api.data)
13
+ elsif api.status == 'error' && api.data['code'] == 'MessageNotFound'
14
+ raise MessageNotFound.new(id)
15
+ else
16
+ raise Error, "Couldn't load message from API (#{api.data})"
17
+ end
18
+ end
19
+
20
+ def self.method_missing(name, *args, &block)
21
+ if MessageScope.instance_methods(false).include?(name)
22
+ Mailflow::Client.instance.messages.send(name, *args, &block)
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ def initialize(client, attributes)
29
+ @client = client
30
+ @attributes = attributes
31
+ @id = @attributes['id']
32
+ @token = @attributes['token']
33
+ @headers = HeaderSet.new(from_expansion(:headers))
34
+ @attachments = from_expansion(:attachments).map { |a| Attachment.new(a) }
35
+ @raw_message = Base64.decode64(from_expansion(:raw_message))
36
+ end
37
+
38
+ ATTRIBUTES = {
39
+ status: [:status, :status],
40
+ last_delivery_attempt: [:status, :last_delivery_attempt, :timestamp],
41
+ held?: [:status, :held, :boolean],
42
+ hold_expiry: [:status, :hold_expiry, :timestamp],
43
+ rcpt_to: [:details, :rcpt_to],
44
+ mail_from: [:details, :mail_from],
45
+ subject: [:details, :subject],
46
+ message_id: [:details, :message_id],
47
+ timestamp: [:details, :timestamp, :timestamp],
48
+ direction: [:details, :direction],
49
+ size: [:details, :size],
50
+ bounce?: [:details, :bounce, :boolean],
51
+ bounce_for_id: [:details, :bounce],
52
+ tag: [:details, :tag],
53
+ received_with_ssl?: [:details, :received_with_ssl, :boolean],
54
+ inspected?: [:inspection, :inspected, :boolean],
55
+ spam?: [:inspection, :spam, :boolean],
56
+ spam_score: [:inspection, :spam_score],
57
+ threat?: [:inspection, :threat, :boolean],
58
+ threat_details: [:inspection, :threat_details],
59
+ plain_body: [:plain_body],
60
+ html_body: [:html_body]
61
+ }
62
+
63
+ def method_missing(name, *args, &block)
64
+ if mapping = ATTRIBUTES[name.to_sym]
65
+ expansion, attribute, type = mapping
66
+ value = from_expansion(expansion, attribute)
67
+ case type
68
+ when :timestamp
69
+ value ? Time.at(value) : nil
70
+ when :boolean
71
+ value == 1
72
+ else
73
+ value
74
+ end
75
+ else
76
+ super
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def from_expansion(expansion, attribute = nil, loaded = false)
83
+ if @attributes.key?(expansion.to_s) || loaded
84
+ attribute ? @attributes[expansion.to_s][attribute.to_s] : @attributes[expansion.to_s]
85
+ else
86
+ load_expansions(expansion)
87
+ from_expansion(expansion, attribute, true)
88
+ end
89
+ end
90
+
91
+ def load_expansions(*names)
92
+ api = @client.moonrope.messages.message(id: self.id, _expansions: names)
93
+ if api.success?
94
+ names.each do |expansion_name|
95
+ if api.data.key?(expansion_name.to_s)
96
+ @attributes[expansion_name.to_s] = api.data[expansion_name.to_s]
97
+ end
98
+ end
99
+ else
100
+ raise Mailflow::Error, "Couldn't load expansion data (#{names.join(', ')}) for message ID '#{self.id}'"
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,26 @@
1
+ require 'mailflow/message'
2
+
3
+ module Mailflow
4
+ class MessageScope
5
+ attr_reader :client
6
+
7
+ def initialize(client)
8
+ @client = client
9
+ @includes = []
10
+ end
11
+
12
+ def includes(*includes)
13
+ @includes.concat(includes)
14
+ self
15
+ end
16
+
17
+ def expansions
18
+ return true if @includes.include?(:all)
19
+ @includes.map(&:to_s)
20
+ end
21
+
22
+ def find_by_id(id)
23
+ Message.find_with_scope(self, id)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,69 @@
1
+ require 'base64'
2
+ require 'mailflow/send_result'
3
+
4
+ module Mailflow
5
+ class SendMessage
6
+ attr_reader :client
7
+ attr_accessor :attributes
8
+
9
+ def initialize(client)
10
+ @client = client
11
+ initialize_attributes
12
+ end
13
+
14
+ def send!
15
+ api = client.moonrope.request(:send, :message, attributes)
16
+ return SendResult.new(client, api.data) if api.success?
17
+
18
+ raise SendError.new(api.data['code'], api.data['message']) if api.status == 'error'
19
+
20
+ raise Error, "API response unsuccessful: #{api.data}"
21
+ end
22
+
23
+ %i[from sender subject tag reply_to plain_body html_body].each do |method_name|
24
+ define_method(method_name) do |value|
25
+ attributes[method_name] = value
26
+ end
27
+ end
28
+
29
+ %i[to cc bcc].each do |method_name|
30
+ define_method(method_name) do |*addresses|
31
+ add_addresses(method_name, addresses)
32
+ end
33
+ end
34
+
35
+ def header(key, value)
36
+ attributes[:headers][key.to_s] = value
37
+ end
38
+
39
+ def attach(filename, content_type, data)
40
+ attributes[:attachments] << {
41
+ name: filename,
42
+ content_type: content_type,
43
+ data: Base64.encode64(data)
44
+ }
45
+ end
46
+
47
+ private
48
+
49
+ def initialize_attributes
50
+ @attributes = {
51
+ to: [],
52
+ cc: [],
53
+ bcc: [],
54
+ headers: {},
55
+ attachments: []
56
+ }
57
+ end
58
+
59
+ def add_addresses(type, addresses)
60
+ sanitized_addresses = sanitize_addresses(addresses)
61
+ attributes[type] += sanitized_addresses
62
+ end
63
+
64
+ def sanitize_addresses(addresses)
65
+ # implement address sanitization logic here
66
+ addresses
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,43 @@
1
+ require 'base64'
2
+ require 'mailflow/send_result'
3
+
4
+ module Mailflow
5
+ class SendRawMessage
6
+ attr_accessor :attributes
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ @attributes = {}
11
+ end
12
+
13
+ def send!
14
+ api = @client.moonrope.request(:send, :raw, @attributes)
15
+ handle_api_response(api)
16
+ end
17
+
18
+ def mail_from(address)
19
+ @attributes[:mail_from] = address
20
+ end
21
+
22
+ def rcpt_to(*addresses)
23
+ @attributes[:rcpt_to] ||= []
24
+ @attributes[:rcpt_to] += addresses
25
+ end
26
+
27
+ def data(data)
28
+ @attributes[:data] = Base64.encode64(data)
29
+ end
30
+
31
+ private
32
+
33
+ def handle_api_response(api)
34
+ if api.success?
35
+ SendResult.new(@client, api.data)
36
+ elsif api.status == 'error'
37
+ raise SendError.new(api.data['code'], api.data['message'])
38
+ else
39
+ raise Error, "Couldn't send message"
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,27 @@
1
+ module Mailflow
2
+ class SendResult
3
+ attr_reader :message_id, :recipients
4
+
5
+ def initialize(client, result)
6
+ @client = client
7
+ @message_id = result['message_id']
8
+ @recipients = build_recipients(result['messages'])
9
+ end
10
+
11
+ def [](recipient)
12
+ recipients[recipient.to_s.downcase]
13
+ end
14
+
15
+ def size
16
+ recipients.size
17
+ end
18
+
19
+ private
20
+
21
+ def build_recipients(messages)
22
+ messages.each_with_object({}) do |(recipient, message_details), hash|
23
+ hash[recipient.to_s.downcase] = Message.new(@client, message_details)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module Mailflow
2
+ VERSION = '0.0.1'
3
+ end
data/lib/mailflow.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'mailflow'
2
+ require 'mailflow/client'
3
+ require 'mailflow/config'
4
+
5
+ module Mailflow
6
+ class << self
7
+ attr_reader :config
8
+
9
+ def configure
10
+ @config ||= Config.new
11
+ yield config if block_given?
12
+ end
13
+
14
+ def send_message(&block)
15
+ Mailflow::Client.instance.send_message(&block)
16
+ end
17
+
18
+ def send_raw_message(&block)
19
+ Mailflow::Client.instance.send_raw_message(&block)
20
+ end
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mailflow
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Mailflow
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-09-28 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.5
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.5
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.1'
33
+ description: Mailflow Ruby library
34
+ email:
35
+ - support@mailflow.cloud
36
+ executables: []
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - lib/mailflow.rb
41
+ - lib/mailflow/attachment.rb
42
+ - lib/mailflow/client.rb
43
+ - lib/mailflow/config.rb
44
+ - lib/mailflow/error.rb
45
+ - lib/mailflow/header_set.rb
46
+ - lib/mailflow/message.rb
47
+ - lib/mailflow/message_scope.rb
48
+ - lib/mailflow/send_message.rb
49
+ - lib/mailflow/send_raw_message.rb
50
+ - lib/mailflow/send_result.rb
51
+ - lib/mailflow/version.rb
52
+ homepage: https://mailflow.cloud
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
+ rubygems_version: 3.2.15
72
+ signing_key:
73
+ specification_version: 4
74
+ summary: Mailflow Ruby library
75
+ test_files: []