├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── MIT-LICENSE ├── README.md ├── Rakefile ├── app ├── mailers │ └── mailhopper │ │ └── mailer.rb └── models │ └── mailhopper │ └── email.rb ├── config └── routes.rb ├── lib ├── generators │ └── mailhopper │ │ ├── mailhopper_generator.rb │ │ └── templates │ │ ├── README │ │ ├── initializer.rb │ │ └── migrations │ │ └── create_emails.rb ├── mailhopper.rb └── mailhopper │ ├── base.rb │ ├── engine.rb │ ├── queue.rb │ └── version.rb ├── mailhopper.gemspec ├── script └── rails └── spec ├── dummy ├── Rakefile ├── app │ ├── assets │ │ ├── javascripts │ │ │ └── application.js │ │ └── stylesheets │ │ │ └── application.css │ ├── controllers │ │ └── application_controller.rb │ ├── helpers │ │ └── application_helper.rb │ ├── mailers │ │ └── sample_mailer.rb │ ├── models │ │ └── .gitkeep │ └── views │ │ ├── layouts │ │ └── application.html.erb │ │ └── sample_mailer │ │ └── hello.text.erb ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── backtrace_silencers.rb │ │ ├── inflections.rb │ │ ├── mailhopper.rb │ │ ├── mime_types.rb │ │ ├── secret_token.rb │ │ ├── session_store.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ └── en.yml │ └── routes.rb ├── db │ ├── migrate │ │ └── 20110916191655_create_emails.rb │ └── schema.rb ├── lib │ └── assets │ │ └── .gitkeep ├── log │ └── .gitkeep ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ └── favicon.ico └── script │ └── rails ├── models └── email_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | .bundle/ 3 | log/*.log 4 | pkg/ 5 | spec/dummy/db/*.sqlite3 6 | spec/dummy/log/*.log 7 | spec/dummy/tmp/ -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_script: "bundle exec rake db:migrate RAILS_ENV=test" 2 | script: "bundle exec rake spec" 3 | rvm: 4 | - 1.8.7 5 | - 1.9.2 6 | - 1.9.3 7 | - 2.0.0 8 | - ree 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | # Declare your gem's dependencies in mailhopper.gemspec. 4 | # Bundler will treat runtime dependencies like base dependencies, and 5 | # development dependencies will be added by default to the :development group. 6 | gemspec 7 | 8 | # Declare any dependencies that are still in development here instead of in 9 | # your gemspec. These might include edge Rails or gems from your path or 10 | # Git. Remember to move these dependencies to your gemspec before releasing 11 | # your gem to rubygems.org. 12 | 13 | # Mailhopper is compatible with rails >= 4, but that does not work with Ruby < 1.9. So, to allow CI builds on those versions, 14 | # we gotta stick with the 3.x line. 15 | gem 'activesupport', '~> 3.2.0' -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011 Cerebris Corporation 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mailhopper [![Build Status](https://secure.travis-ci.org/cerebris/mailhopper.png)](http://travis-ci.org/cerebris/mailhopper) 2 | 3 | Mailhopper provides a simple ActiveRecord-based queue for asynchronous delivery of email in Rails apps. 4 | 5 | Why use Mailhopper to queue your email? 6 | 7 | * Mailhopper captures the full content and headers of emails at their time of creation. It can handle multiple MIME types and attachments. 8 | * If email can't be delivered from your queue (e.g. your smtp server is down), you can retry delivery until successful. 9 | * Emails can be accessed at any time, even after they've been sent. 10 | 11 | The complete rationale is explained in this blog post: http://www.cerebris.com/blog/2011/09/07/tame-rails-email-dragons-with-mailhopper/ 12 | 13 | Mailhopper is intended to be used along with a delivery agent such as DelayedMailhopper, which uses DelayedJob to deliver email from the Mailhopper queue. Without a delivery agent, emails will accumulate in the Mailhopper queue but won't be delivered. 14 | 15 | DelayedMailhopper can be found here: https://github.com/cerebris/delayed_mailhopper 16 | 17 | ## Requirements 18 | 19 | Rails 3.1+ 20 | 21 | ## Installation 22 | 23 | Add to your project's Gemfile: 24 | 25 | ``` 26 | gem 'mailhopper' 27 | ``` 28 | 29 | Install with bundler: 30 | 31 | ``` 32 | bundle install 33 | ``` 34 | 35 | Generate default initializer and migration files: 36 | 37 | ``` 38 | rails generate mailhopper 39 | ``` 40 | 41 | *Before migrating your database, please take a moment to review the `CreateEmails` migration that has been generated.* In particular, please review the limit to the `content` field, which has been set to a safe but very large size (100MB characters). You may wish to change this based upon your needs and particular database. 42 | 43 | When you're ready, migrate your database: 44 | 45 | ``` 46 | rake db:migrate 47 | ``` 48 | 49 | Don't forget to also install a delivery agent, such as DelayedMailhopper, so that emails will be delivered from your queue !!! 50 | 51 | ## Configuration 52 | 53 | If you want all of your application's email to be queued with Mailhopper, configure mailers either in application.rb or your application's environment-specific configuration files: 54 | 55 | ``` 56 | MyApp::Application.configure do 57 | config.action_mailer.delivery_method = :mailhopper 58 | end 59 | ``` 60 | 61 | Alternatively, or additionally, configure individual mailers to use Mailhopper: 62 | 63 | ``` 64 | class MyMailer < ActionMailer::Base 65 | ActionMailer::Base.delivery_method = :mailhopper 66 | end 67 | ``` 68 | 69 | ## Options 70 | 71 | The following options can be configured in your initializer (config/initializers/mailhopper): 72 | 73 | ``` 74 | Mailhopper::Base.setup do |config| 75 | # The base email class used by Mailhopper 76 | config.email_class = Mailhopper::Email 77 | 78 | # The base mailer class used by Mailhopper 79 | config.mailer_class = Mailhopper::Mailer 80 | 81 | # The method used by the delivery agent to deliver emails from your queue 82 | config.default_delivery_method = :smtp 83 | end 84 | ``` 85 | 86 | It's preferable to leave these options out of your initializer if the defaults, shown above, are acceptable. Delivery agents may override some defaults (e.g. DelayedMailhopper sets email_class = DelayedMailhopper::Email). 87 | 88 | ## Copyright 89 | 90 | Copyright (c) 2011 Cerebris Corporation. This is free software released under the MIT License (see MIT-LICENSE for details). 91 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | begin 3 | require 'bundler/setup' 4 | Bundler::GemHelper.install_tasks 5 | rescue LoadError 6 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 7 | end 8 | 9 | APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__) 10 | load 'rails/tasks/engine.rake' 11 | 12 | require 'rspec/core/rake_task' 13 | RSpec::Core::RakeTask.new(:spec) 14 | 15 | task :default => :spec 16 | -------------------------------------------------------------------------------- /app/mailers/mailhopper/mailer.rb: -------------------------------------------------------------------------------- 1 | module Mailhopper 2 | class Mailer < ActionMailer::Base 3 | end 4 | end -------------------------------------------------------------------------------- /app/models/mailhopper/email.rb: -------------------------------------------------------------------------------- 1 | module Mailhopper 2 | class Email < ActiveRecord::Base 3 | # Starting from Rails 4 attr_accessible is deprecated in favour of Strong parameters 4 | if Rails::VERSION::MAJOR < 4 5 | attr_accessible :to_address, :from_address, :cc_address, :bcc_address, 6 | :reply_to_address, :subject, :content 7 | end 8 | 9 | default_scope lambda { order('created_at DESC') } 10 | scope :unsent, lambda { where(:sent_at => nil) } 11 | 12 | validates :from_address, :presence => true 13 | 14 | def send!(delivery_method = nil) 15 | mail = Mail.new(self.content) 16 | mail[:bcc] = self.bcc_address unless self.bcc_address.blank? 17 | Base.mailer_class.wrap_delivery_behavior(mail, delivery_method || Base.default_delivery_method) 18 | mail.deliver 19 | self.sent_at = Time.now 20 | self.save! 21 | end 22 | 23 | class << self 24 | def create_from_mail(mail) 25 | create({ 26 | :to_address => address_to_s(mail.to), 27 | :from_address => address_to_s(mail.from), 28 | :cc_address => address_to_s(mail.cc), 29 | :bcc_address => address_to_s(mail.bcc), 30 | :reply_to_address => address_to_s(mail.reply_to), 31 | :subject => mail.subject, 32 | :content => mail.to_s 33 | }) 34 | end 35 | 36 | private 37 | 38 | def address_to_s(field) 39 | if field 40 | if field.is_a?(Array) 41 | field.join(',') unless field.empty? 42 | else 43 | field 44 | end 45 | end 46 | end 47 | end 48 | end 49 | end -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | end 3 | -------------------------------------------------------------------------------- /lib/generators/mailhopper/mailhopper_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators' 2 | require 'rails/generators/migration' 3 | 4 | module Mailhopper 5 | module Generators 6 | class MailhopperGenerator < Rails::Generators::Base 7 | include Rails::Generators::Migration 8 | 9 | desc 'Generates Mailhopper files.' 10 | 11 | def self.source_root 12 | File.join(File.dirname(__FILE__), 'templates') 13 | end 14 | 15 | def self.next_migration_number(dirname) 16 | if ActiveRecord::Base.timestamped_migrations 17 | Time.now.utc.strftime("%Y%m%d%H%M%S") 18 | else 19 | "%.3d" % (current_migration_number(dirname) + 1) 20 | end 21 | end 22 | 23 | def create_migration_file 24 | migration_template 'migrations/create_emails.rb', 'db/migrate/create_emails.rb' 25 | end 26 | 27 | def copy_initializer 28 | template 'initializer.rb', 'config/initializers/mailhopper.rb' 29 | end 30 | 31 | def show_readme 32 | readme 'README' if behavior == :invoke 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/generators/mailhopper/templates/README: -------------------------------------------------------------------------------- 1 | 2 | =============================================================================== 3 | Mailhopper has been installed. 4 | =============================================================================== 5 | 6 | To configure your application to use Mailhopper: 7 | 8 | 1) Migrate your database with "rake db:migrate". 9 | 10 | 2) If you want all of your application's email to be queued with Mailhopper, configure mailers either in application.rb or your application's environment-specific configuration files: 11 | 12 | MyApp::Application.configure do 13 | config.action_mailer.delivery_method = :mailhopper 14 | end 15 | 16 | Alternatively, or additionally, configure individual mailers to use Mailhopper: 17 | 18 | class MyMailer < ActionMailer::Base 19 | ActionMailer::Base.delivery_method = :mailhopper 20 | end 21 | 22 | 3) Install a delivery agent, such as DelayedMailhopper, to deliver emails from your queue. 23 | 24 | 4) Configure a delivery method in "config/initializers/mailhopper" for your delivery agent to use (the default is smtp). 25 | 26 | Mailhopper::Base.setup do |config| 27 | config.default_delivery_method = :smtp 28 | end 29 | -------------------------------------------------------------------------------- /lib/generators/mailhopper/templates/initializer.rb: -------------------------------------------------------------------------------- 1 | Mailhopper::Base.setup do |config| 2 | config.default_delivery_method = :smtp 3 | end -------------------------------------------------------------------------------- /lib/generators/mailhopper/templates/migrations/create_emails.rb: -------------------------------------------------------------------------------- 1 | # Everything listed in this migration will be added to a migration file 2 | # inside of your main app. 3 | class CreateEmails < ActiveRecord::Migration 4 | def self.up 5 | create_table :emails do |t| 6 | t.string :from_address, :null => false 7 | 8 | t.string :reply_to_address, 9 | :subject 10 | 11 | # The following addresses have been defined as text fields to allow for multiple recipients. These fields could 12 | # instead be defined as strings, and even indexed, if you'd like to improve search performance and you can 13 | # confidently limit the size of their contents. 14 | 15 | t.text :to_address, 16 | :cc_address, 17 | :bcc_address 18 | 19 | # The content field must be large enough to include the full content of emails, including any attachments. If you 20 | # do not plan to send any attachments or long emails, you could leave off this limit. In MySQL, this will result 21 | # in a TEXT column with a limit of 64KB characters. Otherwise, 100MB characters seems a safe limit for almost any 22 | # email. In MySQL, this will result in the creation of a LONGTEXT column with an actual limit of 4GB characters. 23 | 24 | t.text :content, :limit => 100.megabytes 25 | 26 | t.datetime :sent_at 27 | t.timestamps 28 | end 29 | end 30 | 31 | def self.down 32 | drop_table :emails 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/mailhopper.rb: -------------------------------------------------------------------------------- 1 | require "mailhopper/engine" 2 | require 'mailhopper/queue' 3 | require 'mailhopper/base' 4 | 5 | module Mailhopper 6 | class << self 7 | def setup(&block) 8 | Mailhopper::Base.setup(&block) 9 | end 10 | end 11 | end 12 | 13 | ActionMailer::Base.add_delivery_method :mailhopper, Mailhopper::Queue -------------------------------------------------------------------------------- /lib/mailhopper/base.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../../app/models/mailhopper/email', __FILE__) 2 | require File.expand_path('../../../app/mailers/mailhopper/mailer', __FILE__) 3 | 4 | module Mailhopper 5 | class Base 6 | cattr_accessor :email_class 7 | self.email_class = Mailhopper::Email 8 | 9 | cattr_accessor :mailer_class 10 | self.mailer_class = Mailhopper::Mailer 11 | 12 | cattr_accessor :default_delivery_method 13 | self.default_delivery_method = :smtp 14 | 15 | def self.setup 16 | yield self 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /lib/mailhopper/engine.rb: -------------------------------------------------------------------------------- 1 | module Mailhopper 2 | class Engine < Rails::Engine 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /lib/mailhopper/queue.rb: -------------------------------------------------------------------------------- 1 | module Mailhopper 2 | class Queue 3 | def initialize(options) 4 | end 5 | 6 | def deliver!(mail) 7 | Base.email_class.create_from_mail(mail) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/mailhopper/version.rb: -------------------------------------------------------------------------------- 1 | module Mailhopper 2 | VERSION = "0.3.0" 3 | end 4 | -------------------------------------------------------------------------------- /mailhopper.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | 3 | # Maintain your gem's version: 4 | require "mailhopper/version" 5 | 6 | # Describe your gem and declare its dependencies: 7 | Gem::Specification.new do |s| 8 | s.name = "mailhopper" 9 | s.version = Mailhopper::VERSION 10 | s.authors = ["Dan Gebhardt"] 11 | s.email = ["support@cerebris.com"] 12 | s.homepage = "https://github.com/cerebris/mailhopper" 13 | s.summary = "A simple ActiveRecord-based email queue for Rails apps." 14 | s.description = "Mailhopper stores your application's emails in an ActiveRecord queue for asynchronous delivery. Use Mailhopper in combination with a delivery agent such as DelayedMailhopper." 15 | 16 | s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.md"] 17 | s.test_files = Dir["spec/**/*"] 18 | 19 | s.add_dependency "rails", ">= 3.1.0" 20 | 21 | s.add_development_dependency "sqlite3", ">= 1.3.4" 22 | s.add_development_dependency "rspec-rails" 23 | end 24 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #!/usr/bin/env ruby 3 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 4 | 5 | ENGINE_PATH = File.expand_path('../..', __FILE__) 6 | load File.expand_path('../../test/dummy/script/rails', __FILE__) 7 | -------------------------------------------------------------------------------- /spec/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | 7 | Dummy::Application.load_tasks 8 | -------------------------------------------------------------------------------- /spec/dummy/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into including all the files listed below. 2 | // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically 3 | // be included in the compiled file accessible from http://example.com/assets/application.js 4 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 5 | // the compiled file. 6 | // 7 | //= require jquery 8 | //= require jquery_ujs 9 | //= require_tree . 10 | -------------------------------------------------------------------------------- /spec/dummy/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll automatically include all the stylesheets available in this directory 3 | * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at 4 | * the top of the compiled file, but it's generally better to create a new file per style scope. 5 | *= require_self 6 | *= require_tree . 7 | */ -------------------------------------------------------------------------------- /spec/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | end 4 | -------------------------------------------------------------------------------- /spec/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /spec/dummy/app/mailers/sample_mailer.rb: -------------------------------------------------------------------------------- 1 | class SampleMailer < ActionMailer::Base 2 | ActionMailer::Base.delivery_method = :mailhopper 3 | 4 | def hello(headers, content) 5 | @content = content 6 | mail(headers) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/dummy/app/models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cerebris/mailhopper/eb7c402b5bb42b3b8408da2ae643ca1d8c73116f/spec/dummy/app/models/.gitkeep -------------------------------------------------------------------------------- /spec/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dummy 5 | <%= stylesheet_link_tag "application" %> 6 | <%= javascript_include_tag "application" %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /spec/dummy/app/views/sample_mailer/hello.text.erb: -------------------------------------------------------------------------------- 1 | <%= @content %> -------------------------------------------------------------------------------- /spec/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Dummy::Application 5 | -------------------------------------------------------------------------------- /spec/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | Bundler.require 6 | require "mailhopper" 7 | 8 | module Dummy 9 | class Application < Rails::Application 10 | # Settings in config/environments/* take precedence over those specified here. 11 | # Application configuration should go into files in config/initializers 12 | # -- all .rb files in that directory are automatically loaded. 13 | 14 | # Custom directories with classes and modules you want to be autoloadable. 15 | # config.autoload_paths += %W(#{config.root}/extras) 16 | 17 | # Only load the plugins named here, in the order given (default is alphabetical). 18 | # :all can be used as a placeholder for all plugins not explicitly named. 19 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 20 | 21 | # Activate observers that should always be running. 22 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer 23 | 24 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 25 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 26 | # config.time_zone = 'Central Time (US & Canada)' 27 | 28 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 29 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 30 | # config.i18n.default_locale = :de 31 | 32 | # Configure the default encoding used in templates for Ruby 1.9. 33 | config.encoding = "utf-8" 34 | 35 | # Configure sensitive parameters which will be filtered from the log file. 36 | config.filter_parameters += [:password] 37 | 38 | # Enable the asset pipeline 39 | config.assets.enabled = true 40 | end 41 | end 42 | 43 | -------------------------------------------------------------------------------- /spec/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | gemfile = File.expand_path('../../../../Gemfile', __FILE__) 3 | 4 | if File.exist?(gemfile) 5 | ENV['BUNDLE_GEMFILE'] = gemfile 6 | require 'bundler' 7 | Bundler.setup 8 | end 9 | 10 | $:.unshift File.expand_path('../../../../lib', __FILE__) -------------------------------------------------------------------------------- /spec/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: sqlite3 8 | database: db/development.sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | # Warning: The database defined as "test" will be erased and 13 | # re-generated from your development database when you run "rake". 14 | # Do not set this db to the same as development or production. 15 | test: 16 | adapter: sqlite3 17 | database: db/test.sqlite3 18 | pool: 5 19 | timeout: 5000 20 | 21 | production: 22 | adapter: sqlite3 23 | database: db/production.sqlite3 24 | pool: 5 25 | timeout: 5000 26 | -------------------------------------------------------------------------------- /spec/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | Dummy::Application.initialize! 6 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Show full error reports and disable caching 10 | config.consider_all_requests_local = true 11 | config.action_controller.perform_caching = false 12 | 13 | # Don't care if the mailer can't send 14 | config.action_mailer.raise_delivery_errors = false 15 | 16 | # Print deprecation notices to the Rails logger 17 | config.active_support.deprecation = :log 18 | 19 | # Only use best-standards-support built into browsers 20 | config.action_dispatch.best_standards_support = :builtin 21 | 22 | # Do not compress assets 23 | config.assets.compress = false 24 | 25 | # Do not eager load code on boot. This avoids loading your whole application 26 | # just for the purpose of running a single test. If you are using a tool that 27 | # preloads Rails for running tests, you may have to set it to true. 28 | config.eager_load = false 29 | end 30 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Full error reports are disabled and caching is turned on 8 | config.consider_all_requests_local = false 9 | config.action_controller.perform_caching = true 10 | 11 | # Disable Rails's static asset server (Apache or nginx will already do this) 12 | config.serve_static_assets = false 13 | 14 | # Compress JavaScripts and CSS 15 | config.assets.compress = true 16 | 17 | # Specifies the header that your server uses for sending files 18 | # (comment out if your front-end server doesn't support this) 19 | config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx 20 | 21 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 22 | # the I18n.default_locale when a translation can not be found) 23 | config.i18n.fallbacks = true 24 | 25 | # Send deprecation notices to registered listeners 26 | config.active_support.deprecation = :notify 27 | 28 | # Do not eager load code on boot. This avoids loading your whole application 29 | # just for the purpose of running a single test. If you are using a tool that 30 | # preloads Rails for running tests, you may have to set it to true. 31 | config.eager_load = true 32 | end 33 | -------------------------------------------------------------------------------- /spec/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Configure static asset server for tests with Cache-Control for performance 11 | config.serve_static_assets = true 12 | config.static_cache_control = "public, max-age=3600" 13 | 14 | # Show full error reports and disable caching 15 | config.consider_all_requests_local = true 16 | config.action_controller.perform_caching = false 17 | 18 | # Raise exceptions instead of rendering exception templates 19 | config.action_dispatch.show_exceptions = false 20 | 21 | # Disable request forgery protection in test environment 22 | config.action_controller.allow_forgery_protection = false 23 | 24 | # Tell Action Mailer not to deliver emails to the real world. 25 | # The :test delivery method accumulates sent emails in the 26 | # ActionMailer::Base.deliveries array. 27 | config.action_mailer.delivery_method = :test 28 | 29 | # Use SQL instead of Active Record's schema dumper when creating the test database. 30 | # This is necessary if your schema can't be completely dumped by the schema dumper, 31 | # like if you have constraints or database-specific column types 32 | # config.active_record.schema_format = :sql 33 | 34 | # Print deprecation notices to the stderr 35 | config.active_support.deprecation = :stderr 36 | 37 | # Do not eager load code on boot. This avoids loading your whole application 38 | # just for the purpose of running a single test. If you are using a tool that 39 | # preloads Rails for running tests, you may have to set it to true. 40 | config.eager_load = false 41 | end 42 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/mailhopper.rb: -------------------------------------------------------------------------------- 1 | Mailhopper::Base.setup do |config| 2 | config.default_delivery_method = :smtp 3 | end -------------------------------------------------------------------------------- /spec/dummy/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | Dummy::Application.config.secret_token = 'b9e87a8c887892475e40f46ab1dd85f8b6e291b6fd7a866934eba0467d9780a93e1fcb36760254e3f46dbed3a6fe1d3a2f5677499784ba61b3cc1ceda9cd1fda' 8 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Dummy::Application.config.session_store :cookie_store, :key => '_dummy_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # Dummy::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /spec/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActionController::Base.wrap_parameters :format => [:json] 8 | 9 | # Disable root element in JSON by default. 10 | if defined?(ActiveRecord) 11 | ActiveRecord::Base.include_root_in_json = false 12 | end 13 | -------------------------------------------------------------------------------- /spec/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /spec/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.routes.draw do 2 | # The priority is based upon order of creation: 3 | # first created -> highest priority. 4 | 5 | # Sample of regular route: 6 | # match 'products/:id' => 'catalog#view' 7 | # Keep in mind you can assign values other than :controller and :action 8 | 9 | # Sample of named route: 10 | # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase 11 | # This route can be invoked with purchase_url(:id => product.id) 12 | 13 | # Sample resource route (maps HTTP verbs to controller actions automatically): 14 | # resources :products 15 | 16 | # Sample resource route with options: 17 | # resources :products do 18 | # member do 19 | # get 'short' 20 | # post 'toggle' 21 | # end 22 | # 23 | # collection do 24 | # get 'sold' 25 | # end 26 | # end 27 | 28 | # Sample resource route with sub-resources: 29 | # resources :products do 30 | # resources :comments, :sales 31 | # resource :seller 32 | # end 33 | 34 | # Sample resource route with more complex sub-resources 35 | # resources :products do 36 | # resources :comments 37 | # resources :sales do 38 | # get 'recent', :on => :collection 39 | # end 40 | # end 41 | 42 | # Sample resource route within a namespace: 43 | # namespace :admin do 44 | # # Directs /admin/products/* to Admin::ProductsController 45 | # # (app/controllers/admin/products_controller.rb) 46 | # resources :products 47 | # end 48 | 49 | # You can have the root of your site routed with "root" 50 | # just remember to delete public/index.html. 51 | # root :to => 'welcome#index' 52 | 53 | # See how all your routes lay out with "rake routes" 54 | 55 | # This is a legacy wild controller route that's not recommended for RESTful applications. 56 | # Note: This route will make all actions in every controller accessible via GET requests. 57 | # match ':controller(/:action(/:id(.:format)))' 58 | end 59 | -------------------------------------------------------------------------------- /spec/dummy/db/migrate/20110916191655_create_emails.rb: -------------------------------------------------------------------------------- 1 | # Everything listed in this migration will be added to a migration file 2 | # inside of your main app. 3 | class CreateEmails < ActiveRecord::Migration 4 | def self.up 5 | create_table :emails do |t| 6 | t.string :from_address, :null => false 7 | 8 | t.string :reply_to_address, 9 | :subject 10 | 11 | # The following addresses have been defined as text fields to allow for multiple recipients. These fields could 12 | # instead be defined as strings, and even indexed, if you'd like to improve search performance and you can 13 | # confidently limit the size of their contents. 14 | 15 | t.text :to_address, 16 | :cc_address, 17 | :bcc_address 18 | 19 | t.text :content 20 | 21 | t.datetime :sent_at 22 | t.timestamps 23 | end 24 | end 25 | 26 | def self.down 27 | drop_table :emails 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended to check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(:version => 20110916191655) do 15 | 16 | create_table "emails", :force => true do |t| 17 | t.string "from_address", :null => false 18 | t.string "reply_to_address" 19 | t.string "subject" 20 | t.text "to_address" 21 | t.text "cc_address" 22 | t.text "bcc_address" 23 | t.text "content" 24 | t.datetime "sent_at" 25 | t.datetime "created_at", :null => false 26 | t.datetime "updated_at", :null => false 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /spec/dummy/lib/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cerebris/mailhopper/eb7c402b5bb42b3b8408da2ae643ca1d8c73116f/spec/dummy/lib/assets/.gitkeep -------------------------------------------------------------------------------- /spec/dummy/log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cerebris/mailhopper/eb7c402b5bb42b3b8408da2ae643ca1d8c73116f/spec/dummy/log/.gitkeep -------------------------------------------------------------------------------- /spec/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /spec/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /spec/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |

We've been notified about this issue and we'll take a look at it shortly.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /spec/dummy/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cerebris/mailhopper/eb7c402b5bb42b3b8408da2ae643ca1d8c73116f/spec/dummy/public/favicon.ico -------------------------------------------------------------------------------- /spec/dummy/script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /spec/models/email_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Mailhopper::Email do 4 | let(:email) { Mailhopper::Email.new } 5 | 6 | it "should require a from address" do 7 | assert !email.valid? 8 | email.from_address = 'user@example.com' 9 | assert email.valid? 10 | end 11 | 12 | it "should be generated when mail is sent to individual addresses" do 13 | headers = { 14 | :from => 'from@example.com', 15 | :to => 'to@example.com', 16 | :cc => 'cc@example.com', 17 | :bcc => 'bcc@example.com', 18 | :reply_to => 'reply_to@example.com', 19 | :subject => 'Hiya!' 20 | } 21 | content = 'Papaya' 22 | 23 | generate_and_verify_email(headers, content) 24 | end 25 | 26 | it "should be generated when mail is sent to multiple addresses" do 27 | headers = { 28 | :from => 'from1@example.com', 29 | :to => ['to1@example.com', 'to2@example.com'], 30 | :cc => ['cc1@example.com', 'cc2@example.com'], 31 | :bcc => ['bcc1@example.com', 'bcc2@example.com'], 32 | :reply_to => 'reply_to@example.com', 33 | :subject => 'Hiya!' 34 | } 35 | content = 'Papaya' 36 | 37 | generate_and_verify_email(headers, content) 38 | end 39 | 40 | it "should be generated when mail is sent to blank addresses" do 41 | headers = { 42 | :from => 'from@example.com', 43 | :to => 'to@example.com', 44 | :cc => nil, 45 | :bcc => nil, 46 | :reply_to => nil, 47 | :subject => 'Hiya!' 48 | } 49 | content = 'Papaya' 50 | 51 | generate_and_verify_email(headers, content) 52 | end 53 | 54 | it "should be generated even when mail is sent to invalid addresses" do 55 | headers = { 56 | :from => 'from.@example.com', 57 | :to => 'to@to@example.com', 58 | :cc => nil, 59 | :bcc => nil, 60 | :reply_to => nil, 61 | :subject => 'Hiya!' 62 | } 63 | content = 'Papaya' 64 | 65 | generate_and_verify_email(headers, content) 66 | end 67 | 68 | describe '#create_from_mail' do 69 | 70 | subject { Mailhopper::Email } 71 | 72 | let(:mail) do 73 | double('mail', { 74 | :from => 'from', 75 | :to => 'to', 76 | :cc => 'cc', 77 | :bcc => 'bcc', 78 | :reply_to => 'reply_to', 79 | :subject => 'subject', 80 | :to_s => 'content' 81 | }) 82 | end 83 | 84 | it 'creates successully with all data populated right' do 85 | email = subject.send(:create_from_mail, mail) 86 | assert email.valid? 87 | assert_equal 'to', email.to_address 88 | assert_equal 'from', email.from_address 89 | assert_equal 'cc', email.cc_address 90 | assert_equal 'bcc', email.bcc_address 91 | assert_equal 'reply_to', email.reply_to_address 92 | assert_equal 'subject', email.subject 93 | assert_equal 'content', email.content 94 | end 95 | end 96 | end 97 | 98 | private 99 | 100 | def generate_and_verify_email(headers, content) 101 | assert_equal Mailhopper::Email.count, 0 102 | SampleMailer.hello(headers, content).deliver 103 | assert_equal Mailhopper::Email.count, 1 104 | 105 | email = Mailhopper::Email.first 106 | 107 | assert_email_matches_headers(headers, email) 108 | 109 | # Email content will include headers as well as content 110 | assert email.content.include?(content) 111 | 112 | assert email.sent_at.nil? 113 | end 114 | 115 | def assert_email_matches_headers(headers, email) 116 | assert_address_matches_header headers[:from], email.from_address 117 | assert_address_matches_header headers[:to], email.to_address 118 | assert_address_matches_header headers[:cc], email.cc_address 119 | assert_address_matches_header headers[:bcc], email.bcc_address 120 | assert_equal headers[:reply_to], email.reply_to_address 121 | assert_equal headers[:subject], email.subject 122 | end 123 | 124 | def assert_address_matches_header(header, address) 125 | if header.kind_of? Array 126 | assert_equal header.join(','), address 127 | else 128 | assert_equal header, address 129 | end 130 | end 131 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | ENV["RAILS_ENV"] ||= 'test' 3 | require File.expand_path("../../spec/dummy/config/environment", __FILE__) 4 | require 'rspec/rails' 5 | require 'rspec/autorun' 6 | 7 | # Requires supporting ruby files with custom matchers and macros, etc, 8 | # in spec/support/ and its subdirectories. 9 | Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} 10 | 11 | RSpec.configure do |config| 12 | # ## Mock Framework 13 | # 14 | # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line: 15 | # 16 | # config.mock_with :mocha 17 | # config.mock_with :flexmock 18 | # config.mock_with :rr 19 | 20 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 21 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 22 | 23 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 24 | # examples within a transaction, remove the following line or assign false 25 | # instead of true. 26 | config.use_transactional_fixtures = true 27 | 28 | # If true, the base class of anonymous controllers will be inferred 29 | # automatically. This will be the default behavior in future versions of 30 | # rspec-rails. 31 | config.infer_base_class_for_anonymous_controllers = false 32 | end 33 | --------------------------------------------------------------------------------