diff --git a/README.md b/README.md index e61cd984..fd448d56 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ MailCatcher runs a super simple SMTP server which catches any message sent to it * Sendmail-analogue command, `catchmail`, makes [using mailcatcher from PHP][withphp] a lot easier. * Written super-simply in EventMachine, easy to dig in and change. * Keyboard navigation between messages +* Can deliver caught messages to an SMTP server ## How diff --git a/lib/mail_catcher.rb b/lib/mail_catcher.rb index f47115c1..71e7c7ff 100644 --- a/lib/mail_catcher.rb +++ b/lib/mail_catcher.rb @@ -55,6 +55,12 @@ def browse url :daemon => !windows?, :growl => growlnotify?, :browse => false, + :delivery_address => 'smtp.gmail.com', + :delivery_port => '587', + :delivery_domain => 'gmail.com', + :delivery_user_name => '', + :delivery_password => '', + :delivery_recipient => nil, } def parse! arguments=ARGV, defaults=@defaults @@ -83,6 +89,30 @@ def parse! arguments=ARGV, defaults=@defaults options[:http_port] = port end + parser.on("--delivery-address ADDRESS", "Set the SMTP address value for message delivery") do |delivery_address| + options[:delivery_address] = delivery_address + end + + parser.on("--delivery-port PORT", Integer, "Set the SMTP port for message delivery") do |delivery_port| + options[:delivery_port] = delivery_port + end + + parser.on("--delivery-domain DOMAIN", "Set the SMTP domain for message delivery") do |delivery_domain| + options[:delivery_domain] = delivery_domain + end + + parser.on("--delivery-user-name USERNAME", "Set the SMTP user name for message delivery") do |delivery_user_name| + options[:delivery_user_name] = delivery_user_name + end + + parser.on("--delivery-password USERNAME", "Set the SMTP password for message delivery") do |delivery_password| + options[:delivery_password] = delivery_password + end + + parser.on("--delivery-recipient EMAIL", "(Optional) overwrite the to field for message delivery with the provided value") do |delivery_recipient| + options[:delivery_recipient] = delivery_recipient + end + if mac? parser.on("--[no-]growl", "Growl to the local machine when a message arrives") do |growl| if growl and not growlnotify? @@ -126,6 +156,8 @@ def run! options=nil # Otherwise, parse them from ARGV options ||= parse! + DeliveryService.configure(options) + puts "Starting MailCatcher" Thin::Logging.silent = true @@ -190,6 +222,7 @@ def rescue_port port end end +require 'mail_catcher/delivery_service' require 'mail_catcher/events' require 'mail_catcher/growl' require 'mail_catcher/mail' diff --git a/lib/mail_catcher/delivery_service.rb b/lib/mail_catcher/delivery_service.rb new file mode 100644 index 00000000..71b66405 --- /dev/null +++ b/lib/mail_catcher/delivery_service.rb @@ -0,0 +1,40 @@ +class MailCatcher::DeliveryService + attr_reader :message + + mattr_accessor :address + mattr_accessor :port + mattr_accessor :domain + mattr_accessor :user_name + mattr_accessor :password + mattr_accessor :recipient + + mattr_accessor :authentication # authentication is currently hard-coded + @@authentication = 'login' + + def initialize(message) + @message = message + end + + def config + self.class + end + + def deliver! + smtp = Net::SMTP.new config.address, config.port + smtp.enable_starttls + smtp.start(config.domain, config.user_name, config.password, config.authentication) do |smtp| + smtp.send_message message['source'], config.user_name, config.recipient || message['recipients'] + end + end + + class << self + def configure(options = {}) + @@address = options[:delivery_address] + @@port = options[:delivery_port] + @@domain = options[:delivery_domain] + @@user_name = options[:delivery_user_name] + @@password = options[:delivery_password] + @@recipient = options[:delivery_recipient] + end + end +end diff --git a/lib/mail_catcher/web.rb b/lib/mail_catcher/web.rb index 64fb5130..9b0e162d 100644 --- a/lib/mail_catcher/web.rb +++ b/lib/mail_catcher/web.rb @@ -132,6 +132,21 @@ class MailCatcher::Web < Sinatra::Base end end + post "/messages/:id/deliver" do + id = params[:id].to_i + if message = MailCatcher::Mail.message(id) + delivery_service = MailCatcher::DeliveryService.new(message) + begin + delivery_service.deliver! + rescue => e + halt 500, e.inspect + end + "" # Return 200 with an empty body + else + not_found + end + end + not_found do "

No Dice

The message you were looking for does not exist, or doesn't have content of this type.

" end diff --git a/public/javascripts/application.coffee b/public/javascripts/application.coffee index 2ffb749a..9e04e7a7 100644 --- a/public/javascripts/application.coffee +++ b/public/javascripts/application.coffee @@ -190,6 +190,21 @@ class MailCatcher else $('#message .metadata .attachments').hide() + $('#message .views .deliver a').click (e)-> + e.preventDefault() + $deliver = $(this).parent() + deliver_html = $(this).parent().html() + $deliver.text('Delivering...') + $.ajax + url: "/messages/#{id}/deliver" + type: 'POST' + success: => + $deliver.html(deliver_html) + alert 'Message successfully delivered' + error: => + $deliver.html(deliver_html) + alert 'An error occurred while attempting to deliver this message' + $('#message .views .download a').attr 'href', "/messages/#{id}.eml" if $('#message .views .tab.analysis.selected').length diff --git a/public/javascripts/application.js b/public/javascripts/application.js index c2efaa3e..aed91186 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -3,18 +3,23 @@ __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; jQuery.expr[':'].icontains = function(a, i, m) { - var _ref, _ref2; - return ((_ref = (_ref2 = a.textContent) != null ? _ref2 : a.innerText) != null ? _ref : "").toUpperCase().indexOf(m[3].toUpperCase()) >= 0; + var _ref, _ref1; + return ((_ref = (_ref1 = a.textContent) != null ? _ref1 : a.innerText) != null ? _ref : "").toUpperCase().indexOf(m[3].toUpperCase()) >= 0; }; MailCatcher = (function() { function MailCatcher() { this.nextTab = __bind(this.nextTab, this); + this.previousTab = __bind(this.previousTab, this); + this.openTab = __bind(this.openTab, this); + this.selectedTab = __bind(this.selectedTab, this); + this.getTab = __bind(this.getTab, this); + var _this = this; $('#messages tr').live('click', function(e) { e.preventDefault(); @@ -89,13 +94,17 @@ key('up', function() { var id; id = _this.selectedMessage() || 1; - if (id > 1) id -= 1; + if (id > 1) { + id -= 1; + } return _this.loadMessage(id); }); key('down', function() { var id; id = _this.selectedMessage() || _this.messagesCount(); - if (id < _this.messagesCount()) id += 1; + if (id < _this.messagesCount()) { + id += 1; + } return _this.loadMessage(id); }); key('⌘+up, ctrl+up', function() { @@ -131,7 +140,9 @@ }; MailCatcher.prototype.formatDate = function(date) { - if (typeof date === "string") date && (date = this.parseDate(date)); + if (typeof date === "string") { + date && (date = this.parseDate(date)); + } date && (date = this.offsetTimeZone(date)); return date && (date = date.toString("dddd, d MMM yyyy h:mm:ss tt")); }; @@ -159,7 +170,9 @@ MailCatcher.prototype.previousTab = function(tab) { var i; i = tab || tab === 0 ? tab : this.selectedTab() - 1; - if (i < 0) i = this.tabs().length - 1; + if (i < 0) { + i = this.tabs().length - 1; + } if (this.getTab(i).is(":visible")) { return i; } else { @@ -170,7 +183,9 @@ MailCatcher.prototype.nextTab = function(tab) { var i; i = tab ? tab : this.selectedTab() + 1; - if (i > this.tabs().length - 1) i = 0; + if (i > this.tabs().length - 1) { + i = 0; + } if (this.getTab(i).is(":visible")) { return i; } else { @@ -179,7 +194,9 @@ }; MailCatcher.prototype.haveMessage = function(message) { - if (message.id != null) message = message.id; + if (message.id != null) { + message = message.id; + } return $("#messages tbody tr[data-message-id=\"" + message + "\"]").length > 0; }; @@ -214,7 +231,9 @@ MailCatcher.prototype.loadMessage = function(id) { var _this = this; - if ((id != null ? id.id : void 0) != null) id = id.id; + if ((id != null ? id.id : void 0) != null) { + id = id.id; + } id || (id = $('#messages tr.selected').attr('data-message-id')); if (id != null) { $('#messages tbody tr:not([data-message-id="' + id + '"])').removeClass('selected'); @@ -249,6 +268,26 @@ } else { $('#message .metadata .attachments').hide(); } + $('#message .views .deliver a').click(function(e) { + var $deliver, deliver_html, + _this = this; + e.preventDefault(); + $deliver = $(this).parent(); + deliver_html = $(this).parent().html(); + $deliver.text('Delivering...'); + return $.ajax({ + url: "/messages/" + id + "/deliver", + type: 'POST', + success: function() { + $deliver.html(deliver_html); + return alert('Message successfully delivered'); + }, + error: function() { + $deliver.html(deliver_html); + return alert('An error occurred while attempting to deliver this message'); + } + }); + }); $('#message .views .download a').attr('href', "/messages/" + id + ".eml"); if ($('#message .views .tab.analysis.selected').length) { return _this.loadMessageAnalysis(); @@ -289,7 +328,9 @@ var _this = this; return $.getJSON('/messages', function(messages) { return $.each(messages, function(i, message) { - if (!_this.haveMessage(message)) return _this.addMessage(message); + if (!_this.haveMessage(message)) { + return _this.addMessage(message); + } }); }); }; diff --git a/views/index.haml b/views/index.haml index f83f6766..d8c217d8 100644 --- a/views/index.haml +++ b/views/index.haml @@ -59,4 +59,7 @@ %li.action.download %a.button{:href => '#'} %span Download + %li.action.deliver + %a.button{:href => '#'} + %span Deliver %iframe.body