├── .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 [](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 |
--------------------------------------------------------------------------------