mail_runner 0.1.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 +7 -0
- data/.gitignore +11 -0
- data/README.md +149 -0
- data/Rakefile +9 -0
- data/bin/mail_runner +6 -0
- data/lib/mail_runner/bot_helpers/helpers.rb +95 -0
- data/lib/mail_runner/bot_helpers/runner.rb +53 -0
- data/lib/mail_runner/bot_helpers/tests.rb +43 -0
- data/lib/mail_runner/cli.rb +115 -0
- data/lib/mail_runner/head_manager_bot.rb +102 -0
- data/lib/mail_runner/inbound_manager_bot.rb +45 -0
- data/lib/mail_runner/logging.rb +33 -0
- data/lib/mail_runner/queue_manager_bot.rb +53 -0
- data/lib/mail_runner/stats.rb +25 -0
- data/lib/mail_runner/version.rb +3 -0
- data/lib/mail_runner.rb +22 -0
- data/mail_runner.gemspec +36 -0
- data/test/helper.rb +16 -0
- data/test/test_assets/empty.txt +0 -0
- data/test/test_assets/inline_attachments.email +9684 -0
- data/test/test_assets/test.email +59 -0
- data/test/test_assets/test.json +1 -0
- data/test/test_assets/test_config.yml +19 -0
- data/test/test_assets/with_attachments.email +10360 -0
- data/test/test_bot_helpers.rb +158 -0
- data/test/test_cli.rb +139 -0
- data/test/test_inbound_manager_bot.rb +70 -0
- data/test/test_mail_runner.rb +59 -0
- data/test/test_queue_manager_bot.rb +76 -0
- metadata +215 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c867be342a949392aa028eab45d263043929f6a7
|
4
|
+
data.tar.gz: 3f1eef044f8e09e8783e9b9de6938a7f0dd2248b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2f3766091b2c171e155f6138b6f17df1a82387262e283f4caa5b46d8b64a4fb294bbb9b80bbade7b9049f826c8a61a8c080cf0ac1c00a9f75fc8603e03a0695f
|
7
|
+
data.tar.gz: 7a03821b4edace1182ba1e4c32f3801147c46333635e3964be3ff228e346a9961258cbb7157724bf4c19a190bb8ba3a4daeb1a277103c812d83eb895145704de
|
data/.gitignore
ADDED
data/README.md
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
#mail_runner
|
2
|
+
**WORK IN PROGRESS**. See Roadmap below for features not yet completed.
|
3
|
+
|
4
|
+
MailRunner acts as your mailman picking up your email from an [MTA](https://en.wikipedia.org/wiki/Message_transfer_agent), such as Postfix, and then delivering it directly to your app sending each email object as json to webhook. You can tell it to deliver locally or send it to any active webhook making it a functional mailserver for several apps.
|
5
|
+
|
6
|
+
MailRunner, although packaged as a gem, only provides a [CLI](https://en.wikipedia.org/wiki/Command-line_interface). You can launch one or several mailrunner bots via the CLI, daemonize them to run permanently or manage them using a process manager such as monit. This also means, you can use it as a standalone ruby mail service alongside apps in any other language.
|
7
|
+
|
8
|
+
###Note:
|
9
|
+
Mailrunner is designed for a very specific use case. If you are looking for a gem to pull email from an existing regular old email account, via pop or imap, this is not the gem for you (check out [Mailroom](https://github.com/tpitale/mail_room)). If you want a super simple setup that allows you to expose a number of different email addresses for recieving mail in your app, and the ability to suport new ones automatically, then MailRunner is for you.
|
10
|
+
|
11
|
+
###Requirements
|
12
|
+
* Postfix MTA setup as to recieve email. Basic setup instructions here.
|
13
|
+
|
14
|
+
###Installation
|
15
|
+
```
|
16
|
+
gem install mailrunner
|
17
|
+
```
|
18
|
+
|
19
|
+
#Usage
|
20
|
+
Mailrunner is built with a CLI that is used to launch each bot. When in doubt about commands, add the -h flag.
|
21
|
+
```
|
22
|
+
mailrunner -h
|
23
|
+
```
|
24
|
+
|
25
|
+
the basic commands
|
26
|
+
|
27
|
+
```
|
28
|
+
Usage: mail_runner [options]
|
29
|
+
-m, --mailbox MAILBOX Name of Mailbox to watch
|
30
|
+
-w, --webhook URL Complete url of webhook to deliver mail to
|
31
|
+
-a, --archive Set to true id you want mail archived.
|
32
|
+
-d, --daemon Daemonize process. Be sure to add logfile path.
|
33
|
+
-L, --logfile Absolute path to log file.
|
34
|
+
-c, --config Path to YAML config file.
|
35
|
+
-v, --verbose Logger runs in debug mode.
|
36
|
+
```
|
37
|
+
|
38
|
+
The mailbox and webhook options are required; all others are optional.
|
39
|
+
|
40
|
+
### basics
|
41
|
+
1 . To launch a basic mailrunner bot
|
42
|
+
|
43
|
+
```
|
44
|
+
mailrunner -m mailbox\_name -w http://127.0.0.1:3000/some_webhook_for_mail
|
45
|
+
```
|
46
|
+
|
47
|
+
* the mailbox_name is a registered account with postfix: i.e. a system account.
|
48
|
+
* Best to create these as users with no login privileges and no home directory. You don't have to do it this way, but one less security issue to worry about and it's sole purpose is a mail pass-through anyway. [Setup Instructions]().
|
49
|
+
|
50
|
+
* the webhook is any webhook.
|
51
|
+
* The example is a local path, but you can easily use any valid url. The url MUST accept both the PUT and the HEAD http methods. The HEAD is used to verify the url upon launching the bot. A simple HEAD example in sintra:
|
52
|
+
|
53
|
+
```
|
54
|
+
head '/some_webhook_for_mail' do
|
55
|
+
status 200
|
56
|
+
end
|
57
|
+
|
58
|
+
```
|
59
|
+
|
60
|
+
2 . Mail is delivered as a standard POST request with a single parameter: mail_runner_event. Each email is a json encoded array with a single item containing a hash with key "msg". Just recieve it, parse it and ...
|
61
|
+
|
62
|
+
```
|
63
|
+
post '/some_webhook_for_mail' do
|
64
|
+
raw_message = params[:mail_runner_envelope]
|
65
|
+
mail = JSON.parse(raw_message)[0]["msg"]
|
66
|
+
```
|
67
|
+
|
68
|
+
From there you can call all variables of the mail object with:
|
69
|
+
```
|
70
|
+
mail["key"]
|
71
|
+
```
|
72
|
+
|
73
|
+
### Format of the mail object
|
74
|
+
The format of the decoded json object is a:
|
75
|
+
|
76
|
+
keys | Value
|
77
|
+
--- | ------
|
78
|
+
**raw_msg**| the full content of the email received.
|
79
|
+
**headers**| an array of the headers received for the email: ‘Dkim-Signature’, ‘Date’, ‘Message-Id’, etc.
|
80
|
+
**from_email**| from email address
|
81
|
+
**from_name**| from name
|
82
|
+
**to**| all recipients
|
83
|
+
**email**| email address where email was received
|
84
|
+
**subject**| the subject line
|
85
|
+
**tags**| any tags applied
|
86
|
+
**sender**| the Mandrill sender of the message
|
87
|
+
**text**| text version of the email body
|
88
|
+
**html**| HTML version of the email body
|
89
|
+
**attachments**| an array of any attachments. If there are no attachments, key is omitted. See format below.
|
90
|
+
**images**| an array of any images. If there are no attachments, key is omitted. See format below.
|
91
|
+
**spam_report**| -Coming Soon. Requires postfix installation with clamAV.
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
When images or attachments are included, each one will contain the following hash:
|
96
|
+
|
97
|
+
keys | Value
|
98
|
+
--- | ------
|
99
|
+
**name**| file name
|
100
|
+
**type**| MIME type
|
101
|
+
**content**| raw content of file
|
102
|
+
**base64**| boolean - Base64 encoded?
|
103
|
+
|
104
|
+
###Delayed Queue
|
105
|
+
If for any reason, mailrunner is not able to deliver the mail to the specified webhook, it will add it to the mailrunner mail queue to process later. The usual reason this occurs is the webhook is unresponsive, it returns an error code or the server is down. Mailrunner will intermittently test the server if this occurs and once it is working properly, it will process the queue. If mail is not being delivered, you can check the mailrunner log for details on what is happening on mailrunners end.
|
106
|
+
|
107
|
+
## Additional Options
|
108
|
+
####Daemonize
|
109
|
+
Use the `-d ` flag to turn mailrunner into a [daemon](https://en.wikipedia.org/wiki/Daemon_(computing)) & keep it running in the the background. When running as a daemon, be sure to set the logfile path using the ` -L ` flag.
|
110
|
+
|
111
|
+
####Logging
|
112
|
+
Mailrunner wil output all logging info to STDOUT if no logfile path is set. To set a logfile path, use the `-L path/to/logfile.log ` flag followed by the absolute path to the logfile location. If the file doesn't exist, it will be created, but the directory path must still be valid.
|
113
|
+
|
114
|
+
####archive
|
115
|
+
####Config
|
116
|
+
|
117
|
+
Mailrunner can also be launched with a config file storing all defaults in one place. When using a config file, you can launch a mailrunner instance with `mailrunner -c /path/to/config.yml` leaving off all typically required flags. [sample config file](https://gist.github.com/kert-io/3d8d24d048dd25801b7f)
|
118
|
+
|
119
|
+
When using a config file you can set your defaults in the config file but still override them for one-off instances using flags. The instance will launch according to the config file, but override only the options passed manually with each flag.
|
120
|
+
|
121
|
+
# Other usage Scenarios
|
122
|
+
####dtach
|
123
|
+
####Monit
|
124
|
+
|
125
|
+
#Roadmap
|
126
|
+
* Archiving
|
127
|
+
* Run from Config file
|
128
|
+
* Single bot managing several mailboxes and webhooks
|
129
|
+
* Run bots from config file
|
130
|
+
* test server
|
131
|
+
|
132
|
+
|
133
|
+
#Testing
|
134
|
+
#Other helpful setup links
|
135
|
+
* Installing Postfix with spamassasin & clamav
|
136
|
+
* Setting up Postfix to work with mailrunner
|
137
|
+
* Setting up Monit to manage your mailrunner processes
|
138
|
+
|
139
|
+
#License
|
140
|
+
(The MIT License)
|
141
|
+
|
142
|
+
Copyright (c) 2015 Kert Heinecke
|
143
|
+
|
144
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
145
|
+
|
146
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
147
|
+
|
148
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
149
|
+
|
data/Rakefile
ADDED
data/bin/mail_runner
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
module BotHelpers
|
2
|
+
module Helpers
|
3
|
+
#Based on Mbox protocol. maildir not yet supported.
|
4
|
+
#? in ?\.?[a-zA-Z]{2,4} makes compatible with local mail and internal process logging.
|
5
|
+
# working test case: http://rubular.com/r/N5iHbxk1q1
|
6
|
+
REGEX_POSTFIX_MESSAGE_DELIMITER =/^From\W\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+?\.?[a-zA-Z]{2,4}\b\W{1,}[a-zA-Z]{3}\W{1,}\w{3}\W{1,}\d{1,2}\W\d{2}:\d{2}:\d{2}\W\d{4}\n/
|
7
|
+
def self.print_monitoring_started_msg(bot)
|
8
|
+
$logger.info("Helpers") { "mailbox: #{bot.mailbox}" }
|
9
|
+
$logger.info("Helpers") { "path: #{bot.webhook}"}
|
10
|
+
$logger.info("Helpers") {"archive: #{bot.archive}"}
|
11
|
+
puts "Getter Bot is on the Job!"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.convert_raw_mail_to_json(mail)
|
15
|
+
mail_array = []
|
16
|
+
header = parse_header(mail.header)
|
17
|
+
from = parse_from(header[:From])
|
18
|
+
attachments = parse_attachments(mail.attachments)
|
19
|
+
|
20
|
+
msg = {
|
21
|
+
:raw_msg => mail.to_s,
|
22
|
+
:headers => header,
|
23
|
+
:from_email => from[1],
|
24
|
+
:from_name => from[0],
|
25
|
+
:to => header[:To],
|
26
|
+
:email => header[:'X-Original-To'],
|
27
|
+
:subject => header[:Subject],
|
28
|
+
:tags => '',
|
29
|
+
:sender => header[:Sender],
|
30
|
+
:spam_report => 'spam report'
|
31
|
+
}
|
32
|
+
|
33
|
+
msg[:text] = mail.text_part.decoded unless mail.text_part.nil?
|
34
|
+
msg[:html] = mail.html_part.decoded unless mail.html_part.nil?
|
35
|
+
#omitted unless attachments
|
36
|
+
msg[:attachments] = attachments[:a_array] unless attachments[:a_array].empty?
|
37
|
+
msg[:images] = attachments[:i_array] unless attachments[:i_array].empty?
|
38
|
+
|
39
|
+
|
40
|
+
hash = {
|
41
|
+
:msg => msg
|
42
|
+
}
|
43
|
+
|
44
|
+
mail_array << hash
|
45
|
+
return mail_array.to_json
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def self.parse_header(header_contents)
|
50
|
+
hash = {}
|
51
|
+
header = header_contents.to_s.split(/\r\n/)
|
52
|
+
header.each do |h|
|
53
|
+
parts = h.split(/:/, 2)
|
54
|
+
unless parts[0].nil? or parts[1].nil?
|
55
|
+
key = parts[0].strip.to_sym
|
56
|
+
next if key =~ /Return-Path/i
|
57
|
+
value = parts[1].strip
|
58
|
+
hash[key] = parts[1].strip
|
59
|
+
end
|
60
|
+
end
|
61
|
+
return hash
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.parse_from(from)
|
65
|
+
from = from.split(/</)
|
66
|
+
from[0] = from[0].strip
|
67
|
+
from[1] = from[1].strip.gsub(/>/,'')
|
68
|
+
return from
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.parse_attachments(attachments)
|
72
|
+
a_array = []
|
73
|
+
i_array = []
|
74
|
+
attachments.each do |att|
|
75
|
+
disposition = att.content_disposition.split(/;/)[0]
|
76
|
+
item = {
|
77
|
+
:name => att.filename,
|
78
|
+
:type => att.content_type.split(/;/)[0],
|
79
|
+
:content => Base64.encode64(att.body.decoded),
|
80
|
+
:base64 => true,
|
81
|
+
:attachment_id => att.content_id
|
82
|
+
}
|
83
|
+
if disposition == 'inline'
|
84
|
+
i_array << item
|
85
|
+
else
|
86
|
+
a_array << item
|
87
|
+
end
|
88
|
+
end
|
89
|
+
return {:a_array => a_array, :i_array => i_array}
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module BotHelpers
|
2
|
+
|
3
|
+
module Runner
|
4
|
+
def self.get_contents_from_mailbox(mailbox)
|
5
|
+
unless File.zero?(mailbox)
|
6
|
+
|
7
|
+
file = File.open(mailbox, 'r+')
|
8
|
+
file.flock(File::LOCK_EX) #lock file so no other process uses it while open. NOTE: lock before read, so other locks can finish executing.
|
9
|
+
raw_contents = file.read # read contents to var so we can release lock and process later
|
10
|
+
file.truncate(0) #clear mbox file once read
|
11
|
+
file.close #close file to release lock prior to processing contents.
|
12
|
+
|
13
|
+
#porcess Mail content into array of individual messages
|
14
|
+
raw_mail = raw_contents.split(BotHelpers::Helpers::REGEX_POSTFIX_MESSAGE_DELIMITER)
|
15
|
+
unless raw_mail.size <= 1
|
16
|
+
raw_mail.shift #remove empty from split
|
17
|
+
end
|
18
|
+
|
19
|
+
$logger.info("Runner") { "#get_contents_from_mailbox:: #{raw_mail.size} Mail messagess retrieved"}
|
20
|
+
return raw_mail
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.post_to_hook(webhook, parcel)
|
25
|
+
begin
|
26
|
+
response = RestClient.post webhook, :mail_runner_envelope => parcel, :content_type => :json, :accept => :json
|
27
|
+
|
28
|
+
$mad_statter.incr_stat("mail delivered")
|
29
|
+
$logger.info("Runner") {
|
30
|
+
"#post_to_hook::response code:#{response.code}\n" +
|
31
|
+
"\tEmail from: #{JSON.parse(parcel)[0]['msg']['from_email']} to: #{JSON.parse(parcel)[0]['msg']['email']}\n" +
|
32
|
+
"\tPosted to: #{webhook}"
|
33
|
+
}
|
34
|
+
|
35
|
+
$logger.debug("Runner") { "#post_to_hook::response header:#{response.headers}"}
|
36
|
+
|
37
|
+
MailRunner.manager_bot.update_webhook_status("live")
|
38
|
+
rescue
|
39
|
+
$logger.error("Runner") { "#post_to_hook::ABORT: Server appears to be down. Make sure the server is running."}
|
40
|
+
MailRunner.manager_bot.update_webhook_status("down")
|
41
|
+
raise ArgumentError
|
42
|
+
end
|
43
|
+
|
44
|
+
unless response.code == 200
|
45
|
+
$logger.error("Runner") { "#post_to_hook::ABORT: Invalid Webhook. Response not 200. NOTE, Must respond to http HEAD method."}
|
46
|
+
raise
|
47
|
+
end
|
48
|
+
return response
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module BotHelpers
|
2
|
+
module Tests #series of initial command validation tests on launch.
|
3
|
+
def self.all_args_included?(args)
|
4
|
+
if args[:mailbox].nil? or args[:webhook].nil?
|
5
|
+
raise ArgumentError, 'You must include mailbox & webhook minimum. Archive argument is optional. Add -h to see help.'
|
6
|
+
end
|
7
|
+
#if args.size > 3
|
8
|
+
# raise ArgumentError, 'You can only include mailbox, webhook & Archive argument. 3 max! Add -h to see help.'
|
9
|
+
#end
|
10
|
+
#?test format of mailbox?
|
11
|
+
#? test valid webhook format?
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.test_mailbox (path)
|
15
|
+
unless File.file?(path)
|
16
|
+
raise ArgumentError, 'ERROR: Mailbox not valid'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.test_webhook(url)
|
21
|
+
begin
|
22
|
+
response = RestClient.head url
|
23
|
+
MailRunner.manager_bot.update_webhook_status("live")
|
24
|
+
rescue
|
25
|
+
raise ArgumentError, "ERROR: \nMake sure the server is running and the webhook exists.\nNOTE: Server must respond to http HEAD method.\nSee README.md for proper setup.\n"
|
26
|
+
end
|
27
|
+
unless response.code == 200
|
28
|
+
raise ArgumentError, "ERROR: Invalid Webhook. NOTE, Must respond to http HEAD method."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.soft_test_webhook(url)
|
33
|
+
begin
|
34
|
+
response = RestClient.head url
|
35
|
+
MailRunner.manager_bot.update_webhook_status("live")
|
36
|
+
$logger.info("ManagerBot") {"webhook status: live"}
|
37
|
+
rescue
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'optparse'
|
3
|
+
module MailRunner
|
4
|
+
class CLI
|
5
|
+
|
6
|
+
|
7
|
+
def self.start(args)
|
8
|
+
options = parse_options(args)
|
9
|
+
initialize_logger(options)
|
10
|
+
set_globals
|
11
|
+
|
12
|
+
@bot = initialize_manager_bot(options)#run first to make sure it runs bot tests prior to daemonizing a process.
|
13
|
+
daemonize if options[:daemon] && options[:daemon] == true
|
14
|
+
@bot.run
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def self.parse_options(argv)
|
19
|
+
opts = {}
|
20
|
+
@parser = OptionParser.new do |o|
|
21
|
+
o.on '-m', '--mailbox MAILBOX', "Name of Mailbox to watch" do |arg|
|
22
|
+
opts[:mailbox] = arg
|
23
|
+
end
|
24
|
+
|
25
|
+
o.on '-w', '--webhook URL', "Complete url of webhook to deliver mail to" do |arg|
|
26
|
+
opts[:webhook] = arg
|
27
|
+
end
|
28
|
+
|
29
|
+
o.on '-a', '--archive', "Set to true id you want mail archived." do |arg|
|
30
|
+
opts[:archive] = arg
|
31
|
+
end
|
32
|
+
|
33
|
+
o.on '-d', '--daemon', "Daemonize process. Be sure to add logfile path." do |arg|
|
34
|
+
opts[:daemon] = arg
|
35
|
+
end
|
36
|
+
|
37
|
+
o.on '-L', '--logfile PATH', "Absolute path to log file." do |arg|
|
38
|
+
opts[:logfile] = arg
|
39
|
+
end
|
40
|
+
|
41
|
+
o.on '-c', '--config PATH', "Path to YAML config file." do |arg|
|
42
|
+
opts[:config] = arg
|
43
|
+
end
|
44
|
+
|
45
|
+
o.on '-v', '--verbose', "Logger runs in debug mode." do |arg|
|
46
|
+
opts[:verbose] = arg
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@parser.parse!(argv)
|
50
|
+
if opts[:config]
|
51
|
+
opts = parse_config(opts[:config]).merge(opts)
|
52
|
+
end
|
53
|
+
opts
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.parse_config(cf_path)
|
57
|
+
YAML.load_file(cf_path)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.initialize_manager_bot(opts)
|
61
|
+
begin
|
62
|
+
bot = MailRunner.initialize_manager_bot
|
63
|
+
bot.verify_and_set(opts)
|
64
|
+
rescue => e
|
65
|
+
puts e.message
|
66
|
+
exit 1
|
67
|
+
end
|
68
|
+
return bot
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.daemonize
|
72
|
+
#eq_to Process.daemon
|
73
|
+
exit if fork
|
74
|
+
Process.setsid
|
75
|
+
exit if fork
|
76
|
+
Dir.chdir "/"
|
77
|
+
STDIN.reopen "/dev/null"
|
78
|
+
STDOUT.reopen "/dev/null", "a"
|
79
|
+
STDERR.reopen "/dev/null", "a"
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.initialize_logger(options)
|
83
|
+
begin
|
84
|
+
MailRunner::Logging.initialize_logger(options[:logfile]) if options[:logfile]
|
85
|
+
MailRunner::Logging.add_log_file_section_header if options[:logfile]
|
86
|
+
MailRunner::Logging.logger.level = ::Logger::DEBUG if options[:verbose]
|
87
|
+
rescue => e #primarily to alert invald log path in case of daemon
|
88
|
+
puts e.message
|
89
|
+
exit 1
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.set_globals
|
94
|
+
MailRunner.set_globals
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.banner
|
98
|
+
%q{
|
99
|
+
m,
|
100
|
+
`$b
|
101
|
+
.ss, $$: .,d$
|
102
|
+
`$$P,d$P' .,md$P"'
|
103
|
+
,$$$$$bmmd$$$P^'
|
104
|
+
.d$$$$$$$$$$P'
|
105
|
+
$$^' `"^$$$' ____ _ _ _ _
|
106
|
+
$: ,$$: / ___|(_) __| | ___| | _(_) __ _
|
107
|
+
`b :$$ \___ \| |/ _` |/ _ \ |/ / |/ _` |
|
108
|
+
$$: ___) | | (_| | __/ <| | (_| |
|
109
|
+
$$ |____/|_|\__,_|\___|_|\_\_|\__, |
|
110
|
+
.d$$ |_|
|
111
|
+
}
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'mail'
|
2
|
+
require 'json'
|
3
|
+
require 'base64'
|
4
|
+
require 'rest-client'
|
5
|
+
require 'redis'
|
6
|
+
|
7
|
+
module MailRunner
|
8
|
+
|
9
|
+
class ManagerBot
|
10
|
+
|
11
|
+
# loads all BotHelper Modules & then extends class.
|
12
|
+
Dir[File.dirname(__FILE__) + '/bot_helpers/*.rb'].each {|file| require file }
|
13
|
+
|
14
|
+
extend BotHelpers
|
15
|
+
|
16
|
+
#used for testing
|
17
|
+
attr_accessor :mailbox
|
18
|
+
attr_accessor :webhook
|
19
|
+
attr_accessor :archive
|
20
|
+
attr_accessor :webhook_status
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@mailbox = nil
|
26
|
+
@webhook = nil
|
27
|
+
@archive = false
|
28
|
+
@webhook_status = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def verify_and_set(opts)
|
33
|
+
parse_options(opts)
|
34
|
+
test_options
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_options(opts)
|
38
|
+
BotHelpers::Tests.all_args_included?(opts)
|
39
|
+
|
40
|
+
@mailbox = "/var/mail/#{opts[:mailbox]}"
|
41
|
+
@webhook = opts[:webhook]
|
42
|
+
@archive = opts[:archive] == "true" ? true : false
|
43
|
+
end
|
44
|
+
|
45
|
+
def update_webhook_status(status)
|
46
|
+
@webhook_status = status
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_options
|
50
|
+
BotHelpers::Tests.test_mailbox(self.mailbox)
|
51
|
+
BotHelpers::Tests.test_webhook(self.webhook)
|
52
|
+
end
|
53
|
+
|
54
|
+
def run
|
55
|
+
BotHelpers::Helpers.print_monitoring_started_msg(self)
|
56
|
+
$mad_statter.incr_stat("runner launched")
|
57
|
+
while true
|
58
|
+
|
59
|
+
delegate_inbound_processing
|
60
|
+
|
61
|
+
if webhook_status == "down"
|
62
|
+
BotHelpers::Tests.soft_test_webhook(self.webhook)
|
63
|
+
elsif delayed_queue?
|
64
|
+
delegate_delayed_queue_processing
|
65
|
+
end
|
66
|
+
|
67
|
+
sleep 5
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
def inbound_manager
|
73
|
+
MailRunner::InboundManagerBot
|
74
|
+
end
|
75
|
+
|
76
|
+
def delegate_inbound_processing
|
77
|
+
begin
|
78
|
+
inbound_manager.process_inbound(mailbox, webhook, archive)
|
79
|
+
rescue Exception => msg
|
80
|
+
puts msg.inspect
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
def queue_manager
|
86
|
+
MailRunner::QueueManagerBot
|
87
|
+
end
|
88
|
+
|
89
|
+
def delayed_queue?
|
90
|
+
queue_manager.queue_length > 0
|
91
|
+
end
|
92
|
+
|
93
|
+
def delegate_delayed_queue_processing
|
94
|
+
begin
|
95
|
+
queue_manager.process_queue
|
96
|
+
rescue Exception => msg
|
97
|
+
puts msg.inspect
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module MailRunner
|
2
|
+
|
3
|
+
module InboundManagerBot
|
4
|
+
|
5
|
+
def self.process_inbound(mailbox, webhook, archive)
|
6
|
+
raw_mail = get_mail(mailbox)
|
7
|
+
|
8
|
+
unless raw_mail.nil?
|
9
|
+
raw_mail.each do |raw_msg|
|
10
|
+
|
11
|
+
mail = read_mail(raw_msg)
|
12
|
+
|
13
|
+
json_packet = BotHelpers::Helpers.convert_raw_mail_to_json(mail)
|
14
|
+
|
15
|
+
deliver_mail(webhook, json_packet)
|
16
|
+
|
17
|
+
if archive == true && queued.nil?
|
18
|
+
puts "we will archive email.\n"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.get_mail(mailbox)
|
26
|
+
BotHelpers::Runner.get_contents_from_mailbox(mailbox)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.read_mail(raw_msg)
|
30
|
+
Mail.read_from_string(raw_msg)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.deliver_mail(webhook, json_packet)
|
34
|
+
begin
|
35
|
+
BotHelpers::Runner.post_to_hook(webhook, json_packet)
|
36
|
+
rescue Exception => msg
|
37
|
+
#interrupt exception here, so rest of inbound mail can be processed and added to queue.
|
38
|
+
#otherwise, it will be lost.
|
39
|
+
queued = MailRunner::QueueManagerBot.add_to_mail_queue(webhook, json_packet)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module MailRunner
|
5
|
+
module Logging
|
6
|
+
|
7
|
+
def self.initialize_logger(log_target = STDOUT)
|
8
|
+
oldlogger = defined?(@logger) ? @logger : nil
|
9
|
+
@logger = Logger.new(log_target, 'weekly')
|
10
|
+
@logger.level = Logger::INFO
|
11
|
+
@logger.formatter = proc do |severity, datetime, progname, msg|
|
12
|
+
date_format = datetime.strftime("%b %d %H:%M:%S ")
|
13
|
+
"#{date_format} #{severity} (#{progname}): #{msg}\n"
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
oldlogger.close if oldlogger && !$TESTING # don't want to close testing's STDOUT logging
|
18
|
+
@logger
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.logger
|
22
|
+
defined?(@logger) ? @logger : initialize_logger
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.logger=(log)
|
26
|
+
@logger = (log ? log : Logger.new('/dev/null'))
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.add_log_file_section_header
|
30
|
+
@logger.info{"\n\nInitiate LogFile :: Session #{$redis.get("MR::sessions").to_i + 1} :: #{Time.now}\n########################################################"}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|