letter_opener-1.10.0/0000755000004100000410000000000014620142103014461 5ustar www-datawww-dataletter_opener-1.10.0/lib/0000755000004100000410000000000014620142103015227 5ustar www-datawww-dataletter_opener-1.10.0/lib/letter_opener.rb0000644000004100000410000000056514620142103020431 0ustar www-datawww-datamodule LetterOpener autoload :Message, "letter_opener/message" autoload :DeliveryMethod, "letter_opener/delivery_method" autoload :Configuration, "letter_opener/configuration" def self.configuration @configuration ||= Configuration.new end def self.configure yield(configuration) end end require "letter_opener/railtie" if defined?(Rails::Railtie) letter_opener-1.10.0/lib/letter_opener/0000755000004100000410000000000014620142103020076 5ustar www-datawww-dataletter_opener-1.10.0/lib/letter_opener/configuration.rb0000644000004100000410000000051114620142103023267 0ustar www-datawww-datamodule LetterOpener class Configuration attr_accessor :location, :message_template, :file_uri_scheme def initialize @location = Rails.root.join('tmp', 'letter_opener') if defined?(Rails) && Rails.respond_to?(:root) && Rails.root @message_template = 'default' @file_uri_scheme = nil end end end letter_opener-1.10.0/lib/letter_opener/templates/0000755000004100000410000000000014620142103022074 5ustar www-datawww-dataletter_opener-1.10.0/lib/letter_opener/templates/default.html.erb0000644000004100000410000000752114620142103025162 0ustar www-datawww-data <% if mail.subject %> <%= h mail.subject %> <% end %>
From:
<%= h from %>
<% unless sender.empty? %>
Sender:
<%= h sender %>
<% end %> <% unless reply_to.empty? %>
Reply-To:
<%= h reply_to %>
<% end %> <% if mail.subject %>
Subject:
<%= h mail.subject %>
<% end %>
Date:
<%= Time.now.strftime("%b %e, %Y %I:%M:%S %p %Z") %>
<% unless to.empty? %>
To:
<%= h to %>
<% end %> <% unless cc.empty? %>
CC:
<%= h cc %>
<% end %> <% unless bcc.empty? %>
BCC:
<%= h bcc %>
<% end %> <% if @attachments.any? %>
Attachments:
<% @attachments.each do |filename, path| %> <%= filename %>  <% end %>
<% end %>
<% if mail.multipart? %>

<% if type == "plain" && mail.html_part %> View HTML version <% elsif type == "rich" && mail.text_part %> View plain text version <% end %>

<% end %>
<% if type == "plain" %>
<%= auto_link(h(body)) %>
<% else %> <% end %>
letter_opener-1.10.0/lib/letter_opener/templates/light.html.erb0000644000004100000410000000035214620142103024640 0ustar www-datawww-data <% if mail.subject %> <%= h mail.subject %> <% end %> <%= body %> letter_opener-1.10.0/lib/letter_opener/tasks/0000755000004100000410000000000014620142103021223 5ustar www-datawww-dataletter_opener-1.10.0/lib/letter_opener/tasks/letter_opener.rake0000644000004100000410000000021414620142103024733 0ustar www-datawww-datanamespace :tmp do task :letter_opener do rm_rf Dir["tmp/letter_opener/[^.]*"], verbose: false end task clear: :letter_opener end letter_opener-1.10.0/lib/letter_opener/delivery_method.rb0000644000004100000410000000245014620142103023607 0ustar www-datawww-datarequire "digest/sha1" require "launchy" module LetterOpener class DeliveryMethod class InvalidOption < StandardError; end attr_accessor :settings def initialize(options = {}) options[:message_template] ||= LetterOpener.configuration.message_template options[:location] ||= LetterOpener.configuration.location options[:file_uri_scheme] ||= LetterOpener.configuration.file_uri_scheme raise InvalidOption, "A location option is required when using the Letter Opener delivery method" if options[:location].nil? self.settings = options end def deliver!(mail) validate_mail!(mail) location = File.join(settings[:location], "#{Time.now.to_f.to_s.tr('.', '_')}_#{Digest::SHA1.hexdigest(mail.encoded)[0..6]}") messages = Message.rendered_messages(mail, location: location, message_template: settings[:message_template]) ::Launchy.open("#{settings[:file_uri_scheme]}#{messages.first.filepath}") end private def validate_mail!(mail) if !mail.smtp_envelope_from || mail.smtp_envelope_from.empty? raise ArgumentError, "SMTP From address may not be blank" end if !mail.smtp_envelope_to || mail.smtp_envelope_to.empty? raise ArgumentError, "SMTP To address may not be blank" end end end end letter_opener-1.10.0/lib/letter_opener/message.rb0000644000004100000410000000703114620142103022050 0ustar www-datawww-datarequire "cgi" require "erb" require "fileutils" require "uri" module LetterOpener class Message attr_reader :mail def self.rendered_messages(mail, options = {}) messages = [] messages << new(mail, options.merge(part: mail.html_part)) if mail.html_part messages << new(mail, options.merge(part: mail.text_part)) if mail.text_part messages << new(mail, options) if messages.empty? messages.each(&:render) messages.sort end ERROR_MSG = '%s or default configuration must be given'.freeze def initialize(mail, options = {}) @mail = mail @location = options[:location] || LetterOpener.configuration.location @part = options[:part] @template = options[:message_template] || LetterOpener.configuration.message_template @attachments = [] raise ArgumentError, ERROR_MSG % 'options[:location]' unless @location raise ArgumentError, ERROR_MSG % 'options[:message_template]' unless @template end def render FileUtils.mkdir_p(@location) if mail.attachments.any? attachments_dir = File.join(@location, 'attachments') FileUtils.mkdir_p(attachments_dir) mail.attachments.each do |attachment| filename = attachment_filename(attachment) path = File.join(attachments_dir, filename) unless File.exist?(path) # true if other parts have already been rendered File.open(path, 'wb') { |f| f.write(attachment.body.raw_source) } end @attachments << [attachment.filename, "attachments/#{filename}"] end end File.open(filepath, 'w') do |f| f.write ERB.new(template).result(binding) end mail["location_#{type}"] = filepath end def template File.read(File.expand_path("../templates/#{@template}.html.erb", __FILE__)) end def filepath File.join(@location, "#{type}.html") end def content_type @part && @part.content_type || @mail.content_type end def body @body ||= begin body = (@part || @mail).decoded mail.attachments.each do |attachment| body.gsub!(attachment.url, "attachments/#{attachment_filename(attachment)}") end body end end def from @from ||= Array(@mail['from']).join(", ") end def sender @sender ||= Array(@mail['sender']).join(", ") end def subject @subject ||= @mail.subject end def to @to ||= Array(@mail['to']).join(", ") end def cc @cc ||= Array(@mail['cc']).join(", ") end def bcc @bcc ||= Array(@mail['bcc']).join(", ") end def reply_to @reply_to ||= Array(@mail['reply-to']).join(", ") end def type content_type =~ /html/ ? "rich" : "plain" end def encoding body.respond_to?(:encoding) ? body.encoding : "utf-8" end def auto_link(text) text.gsub(URI::Parser.new.make_regexp(%W[https http])) do |link| "#{ link }" end end def h(content) CGI.escapeHTML(content) end def attachment_filename(attachment) # Copied from https://github.com/rails/rails/blob/6bfc637659248df5d6719a86d2981b52662d9b50/activestorage/app/models/active_storage/filename.rb#L57 attachment.filename.encode( Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-" ) end def <=>(other) order = %w[rich plain] order.index(type) <=> order.index(other.type) end end end letter_opener-1.10.0/lib/letter_opener/railtie.rb0000644000004100000410000000060014620142103022050 0ustar www-datawww-datamodule LetterOpener class Railtie < Rails::Railtie initializer "letter_opener.add_delivery_method" do ActiveSupport.on_load :action_mailer do ActionMailer::Base.add_delivery_method( :letter_opener, LetterOpener::DeliveryMethod ) end end rake_tasks do load 'letter_opener/tasks/letter_opener.rake' end end end letter_opener-1.10.0/spec/0000755000004100000410000000000014620142103015413 5ustar www-datawww-dataletter_opener-1.10.0/spec/spec_helper.rb0000644000004100000410000000024714620142103020234 0ustar www-datawww-datarequire 'rubygems' require 'bundler/setup' Bundler.require(:default) require "mail" RSpec.configure do |config| config.run_all_when_everything_filtered = true end letter_opener-1.10.0/spec/letter_opener/0000755000004100000410000000000014620142103020262 5ustar www-datawww-dataletter_opener-1.10.0/spec/letter_opener/delivery_method_spec.rb0000644000004100000410000002660214620142103025012 0ustar www-datawww-datarequire "spec_helper" describe LetterOpener::DeliveryMethod do let(:location) { File.expand_path('../../../tmp/letter_opener', __FILE__) } let(:file_uri_scheme) { nil } let(:plain_file) { Dir["#{location}/*/plain.html"].first } let(:plain) { CGI.unescape_html(File.read(plain_file)) } before do allow(Launchy).to receive(:open) FileUtils.rm_rf(location) context = self Mail.defaults do delivery_method LetterOpener::DeliveryMethod, location: context.location, file_uri_scheme: context.file_uri_scheme end end it 'raises an exception if no location passed' do expect { LetterOpener::DeliveryMethod.new }.to raise_exception(LetterOpener::DeliveryMethod::InvalidOption) expect { LetterOpener::DeliveryMethod.new(location: "foo") }.to_not raise_exception end context 'integration' do before do expect(Launchy).to receive(:open).and_call_original ENV['LAUNCHY_DRY_RUN'] = 'true' end context 'normal location path' do it 'opens email' do expect($stdout).to receive(:puts) expect { Mail.deliver do to 'Bar bar@example.com' from 'Foo foo@example.com' body 'World! http://example.com' end }.not_to raise_error end end context 'with spaces in location path' do let(:location) { File.expand_path('../../../tmp/letter_opener with space', __FILE__) } it 'opens email' do expect($stdout).to receive(:puts) expect { Mail.deliver do to 'Bar bar@example.com' from 'Foo foo@example.com' body 'World! http://example.com' end }.not_to raise_error end end end context 'content' do context 'plain' do before do expect(Launchy).to receive(:open) Mail.deliver do from 'Foo ' sender 'Baz ' reply_to 'No Reply ' to 'Bar ' cc 'Qux ' bcc 'Qux ' subject 'Hello' body 'World! http://example.com' end end it 'creates plain html document' do expect(File.exist?(plain_file)).to be_truthy end it 'saves From field' do expect(plain).to include("Foo ") end it 'saves Sender field' do expect(plain).to include("Baz ") end it 'saves Reply-to field' do expect(plain).to include("No Reply ") end it 'saves To field' do expect(plain).to include("Bar ") end it 'saves Subject field' do expect(plain).to include("Hello") end it 'saves Body with autolink' do expect(plain).to include('World! http://example.com') end end context 'multipart' do let(:rich_file) { Dir["#{location}/*/rich.html"].first } let(:rich) { CGI.unescape_html(File.read(rich_file)) } before do expect(Launchy).to receive(:open) Mail.deliver do from 'foo@example.com' to 'bar@example.com' subject 'Many parts with ' text_part do body 'This is text' end html_part do content_type 'text/html; charset=UTF-8' body '

This is HTML

' end end end it 'creates plain html document' do expect(File.exist?(plain_file)).to be_truthy end it 'creates rich html document' do expect(File.exist?(rich_file)).to be_truthy end it 'shows link to rich html version' do expect(plain).to include("View HTML version") end it 'saves text part' do expect(plain).to include("This is text") end it 'saves html part' do expect(rich).to include("

This is HTML

") end it 'saves escaped Subject field' do expect(plain).to include("Many parts with ") end it 'shows subject as title' do expect(rich).to include("Many parts with <html>") end end end context 'document with spaces in name' do let(:location) { File.expand_path('../../../tmp/letter_opener with space', __FILE__) } before do expect(Launchy).to receive(:open) Mail.deliver do from 'Foo ' to 'bar@example.com' subject 'Hello' body 'World!' end end it 'creates plain html document' do File.exist?(plain_file) end it 'saves From filed' do expect(plain).to include("Foo ") end end context 'using deliver! method' do before do expect(Launchy).to receive(:open) Mail.new do from 'foo@example.com' to 'bar@example.com' subject 'Hello' body 'World!' end.deliver! end it 'creates plain html document' do expect(File.exist?(plain_file)).to be_truthy end it 'saves From field' do expect(plain).to include("foo@example.com") end it 'saves To field' do expect(plain).to include("bar@example.com") end it 'saves Subject field' do expect(plain).to include("Hello") end it 'saves Body field' do expect(plain).to include("World!") end end context 'attachments in plain text mail' do before do Mail.deliver do from 'foo@example.com' to 'bar@example.com' subject 'With attachments' text_part do body 'This is text' end attachments[File.basename(__FILE__)] = File.read(__FILE__) end end it 'creates attachments dir with attachment' do attachment = Dir["#{location}/*/attachments/#{File.basename(__FILE__)}"].first expect(File.exist?(attachment)).to be_truthy end it 'saves attachment name' do plain = File.read(Dir["#{location}/*/plain.html"].first) expect(plain).to include(File.basename(__FILE__)) end end context 'attachments in rich mail' do let(:url) { mail.attachments[0].url } let!(:mail) do Mail.deliver do from 'foo@example.com' to 'bar@example.com' subject 'With attachments' attachments[File.basename(__FILE__)] = File.read(__FILE__) url = attachments[0].url html_part do content_type 'text/html; charset=UTF-8' body "Here's an image: " end end end it 'creates attachments dir with attachment' do attachment = Dir["#{location}/*/attachments/#{File.basename(__FILE__)}"].first expect(File.exist?(attachment)).to be_truthy end it 'replaces inline attachment urls' do text = File.read(Dir["#{location}/*/rich.html"].first) expect(mail.parts[0].body).to include(url) expect(text).to_not include(url) expect(text).to include("attachments/#{File.basename(__FILE__)}") end end context 'attachments with non-word characters in the filename' do before do Mail.deliver do from 'foo@example.com' to 'bar@example.com' subject 'With attachments' text_part do body 'This is text' end attachments['non word:chars/used,01-02.txt'] = File.read(__FILE__) url = attachments[0].url html_part do content_type 'text/html; charset=UTF-8' body "This is an image: " end end end it 'creates attachments dir with attachment' do attachment = Dir["#{location}/*/attachments/non word-chars-used,01-02.txt"].first expect(File.exist?(attachment)).to be_truthy end it 'saves attachment name' do plain = File.read(Dir["#{location}/*/plain.html"].first) expect(plain).to include('non word-chars-used,01-02.txt') end it 'replaces inline attachment names' do text = File.read(Dir["#{location}/*/rich.html"].first) expect(text).to include('attachments/non word-chars-used,01-02.txt') end end context 'subjectless mail' do before do expect(Launchy).to receive(:open) Mail.deliver do from 'Foo foo@example.com' reply_to 'No Reply no-reply@example.com' to 'Bar bar@example.com' body 'World! http://example.com' end end it 'creates plain html document' do expect(File.exist?(plain_file)).to be_truthy end end context 'delivery params' do it 'raises an exception if there is no SMTP Envelope To value' do expect(Launchy).not_to receive(:open) expect { Mail.deliver do from 'Foo foo@example.com' reply_to 'No Reply no-reply@example.com' body 'World! http://example.com' end }.to raise_exception(ArgumentError) end it 'does not raise an exception if there is at least one SMTP Envelope To value' do expect(Launchy).to receive(:open) expect { Mail.deliver do from 'Foo foo@example.com' cc 'Bar bar@example.com' reply_to 'No Reply no-reply@example.com' body 'World! http://example.com' end }.not_to raise_exception end end context 'light template' do before do expect(Launchy).to receive(:open) LetterOpener.configure do |config| config.message_template = :light end Mail.defaults do delivery_method LetterOpener::DeliveryMethod, :location => File.expand_path('../../../tmp/letter_opener', __FILE__) end Mail.deliver do subject 'Foo subject' from 'Foo foo@example.com' reply_to 'No Reply no-reply@example.com' to 'Bar bar@example.com' body 'World! http://example.com' end end after do LetterOpener.configure do |config| config.message_template = :default end end it 'creates plain html document' do expect(File.exist?(plain_file)).to be_truthy end end context 'specifying custom file_uri_scheme configuration option' do after do Mail.defaults do delivery_method LetterOpener::DeliveryMethod, location: File.expand_path('../../../tmp/letter_opener', __FILE__) end Mail.deliver do subject 'Foo subject' from 'Foo foo@example.com' reply_to 'No Reply no-reply@example.com' to 'Bar bar@example.com' body 'World! http://example.com' end LetterOpener.configure do |config| config.file_uri_scheme = file_uri_scheme end end context 'file_uri_scheme is not set in configuration' do it "sends the path to Launchy with the 'file://' prefix by default" do allow(Launchy).to receive(:open) do |path| expect(path).not_to match(/^file:\/\//) end end end context 'file_uri_scheme is set in configuration' do it "sends the path to Launchy with the 'file://///wsl$/Ubuntu-18.04' prefix" do allow(Launchy).to receive(:open) do |path| expect(path).to match(/^file:\/\/\/\/\/wsl\$\/Ubuntu-18.04/) end LetterOpener.configure do |config| config.file_uri_scheme = 'file://///wsl$/Ubuntu-18.04' end end end end end letter_opener-1.10.0/spec/letter_opener/message_spec.rb0000644000004100000410000002275114620142103023254 0ustar www-datawww-data# encoding: utf-8 require 'spec_helper' describe LetterOpener::Message do let(:location) { File.expand_path('../../../tmp/letter_opener', __FILE__) } def mail(options={}, &blk) Mail.new(options, &blk) end describe '#reply_to' do it 'handles one email as a string' do mail = mail(:reply_to => 'test@example.com') message = described_class.new(mail, location: location) expect(message.reply_to).to eq('test@example.com') end it 'handles one email with display names' do mail = mail(:reply_to => 'test ') message = described_class.new(mail, location: location) expect(message.reply_to).to eq('test ') end it 'handles array of emails' do mail = mail(:reply_to => ['test1@example.com', 'test2@example.com']) message = described_class.new(mail, location: location) expect(message.reply_to).to eq('test1@example.com, test2@example.com') end it 'handles array of emails with display names' do mail = mail(:reply_to => ['test1 ', 'test2 ']) message = described_class.new(mail, location: location) expect(message.reply_to).to eq('test1 , test2 ') end end describe '#subject' do it 'handles UTF-8 charset subject' do mail = mail(:subject => 'test_mail') message = described_class.new(mail, location: location) expect(message.subject).to eq('test_mail') end it 'handles encode ISO-2022-JP charset subject' do mail = mail(:subject => '=?iso-2022-jp?B?GyRCJUYlOSVIJWEhPCVrGyhC?=') message = described_class.new(mail, location: location) expect(message.subject).to eq('テストメール') end end describe '#to' do it 'handles one email as a string' do mail = mail(:to => 'test@example.com') message = described_class.new(mail, location: location) expect(message.to).to eq('test@example.com') end it 'handles one email with display names' do mail = mail(:to => 'test ') message = described_class.new(mail, location: location) expect(message.to).to eq('test ') end it 'handles array of emails' do mail = mail(:to => ['test1@example.com', 'test2@example.com']) message = described_class.new(mail, location: location) expect(message.to).to eq('test1@example.com, test2@example.com') end it 'handles array of emails with display names' do mail = mail(:to => ['test1 ', 'test2 ']) message = described_class.new(mail, location: location) expect(message.to).to eq('test1 , test2 ') end end describe '#cc' do it 'handles one cc email as a string' do mail = mail(:cc => 'test@example.com') message = described_class.new(mail, location: location) expect(message.cc).to eq('test@example.com') end it 'handles one cc email with display name' do mail = mail(:cc => ['test ', 'test2 ']) message = described_class.new(mail, location: location) expect(message.cc).to eq('test , test2 ') end it 'handles array of cc emails' do mail = mail(:cc => ['test1@example.com', 'test2@example.com']) message = described_class.new(mail, location: location) expect(message.cc).to eq('test1@example.com, test2@example.com') end it 'handles array of cc emails with display names' do mail = mail(:cc => ['test ', 'test2 ']) message = described_class.new(mail, location: location) expect(message.cc).to eq('test , test2 ') end end describe '#bcc' do it 'handles one bcc email as a string' do mail = mail(:bcc => 'test@example.com') message = described_class.new(mail, location: location) expect(message.bcc).to eq('test@example.com') end it 'handles one bcc email with display name' do mail = mail(:bcc => ['test ', 'test2 ']) message = described_class.new(mail, location: location) expect(message.bcc).to eq('test , test2 ') end it 'handles array of bcc emails' do mail = mail(:bcc => ['test1@example.com', 'test2@example.com']) message = described_class.new(mail, location: location) expect(message.bcc).to eq('test1@example.com, test2@example.com') end it 'handles array of bcc emails with display names' do mail = mail(:bcc => ['test ', 'test2 ']) message = described_class.new(mail, location: location) expect(message.bcc).to eq('test , test2 ') end end describe '#sender' do it 'handles one email as a string' do mail = mail(:sender => 'sender@example.com') message = described_class.new(mail, location: location) expect(message.sender).to eq('sender@example.com') end it 'handles one email as a string with display name' do mail = mail(:sender => 'test ') message = described_class.new(mail, location: location) expect(message.sender).to eq('test ') end it 'handles array of emails' do mail = mail(:sender => ['sender1@example.com', 'sender2@example.com']) message = described_class.new(mail, location: location) expect(message.sender).to eq('sender1@example.com, sender2@example.com') end it 'handles array of emails with display names' do mail = mail(:sender => ['test ', 'test2 ']) message = described_class.new(mail, location: location) expect(message.sender).to eq('test , test2 ') end end describe '#<=>' do it 'sorts rich type before plain type' do plain = described_class.new(double(content_type: 'text/plain'), location: location) rich = described_class.new(double(content_type: 'text/html'), location: location) expect([plain, rich].sort).to eq([rich, plain]) end end describe '#auto_link' do let(:message){ described_class.new(mail, location: location) } it 'does not modify unlinkable text' do text = 'the quick brown fox jumped over the lazy dog' expect(message.auto_link(text)).to eq(text) end it 'adds links for http' do raw = "Link to http://localhost:3000/example/path path" linked = "Link to http://localhost:3000/example/path path" expect(message.auto_link(raw)).to eq(linked) end end describe '#body' do it 'handles UTF-8 charset body correctly, with QP CTE, for a non-multipart message' do mail = mail(:sender => 'sender@example.com') do content_type "text/html; charset=UTF-8" content_transfer_encoding 'quoted-printable' body "☃" end message = message = described_class.new(mail, location: location) expect(message.body.encoding.name).to eq('UTF-8') end it 'handles UTF-8 charset HTML part body correctly, with QP CTE, for a multipart message' do mail = mail(:sender => 'sender@example.com') do html_part do content_type "text/html; charset=UTF-8" content_transfer_encoding 'quoted-printable' body "☃" end end message = described_class.new(mail, location: location, part: mail.html_part) expect(message.body.encoding.name).to eq('UTF-8') end it 'handles UTF-8 charset text part body correctly, with QP CTE, for a multipart message' do mail = mail(:sender => 'sender@example.com') do text_part do content_type "text/plain; charset=UTF-8" content_transfer_encoding 'quoted-printable' body "☃" end end message = described_class.new(mail, location: location, part: mail.text_part) expect(message.body.encoding.name).to eq('UTF-8') end end describe '#render' do it 'records the saved email path for plain content type' do mail = mail(:subject => 'test_mail') message = described_class.new(mail, location: location) message.render expect(mail['location_plain'].value).to end_with('tmp/letter_opener/plain.html') end it 'records the saved email path for rich content type' do mail = mail(:content_type => 'text/html', :subject => 'test_mail') message = described_class.new(mail, location: location) message.render expect(mail['location_rich'].value).to end_with('tmp/letter_opener/rich.html') end end describe '.rendered_messages' do it 'uses configured default template if options not given' do allow(LetterOpener.configuration).to receive(:location) { location } messages = described_class.rendered_messages(mail) expect(messages.first.template).not_to be_nil end it 'fails if necessary defaults are not provided' do allow(LetterOpener.configuration).to receive(:location) { nil } expect { described_class.rendered_messages(mail) }.to raise_error(ArgumentError) end it 'fails if necessary defaults are not provided' do allow(LetterOpener.configuration).to receive(:message_template) { nil } expect { described_class.rendered_messages(mail) }.to raise_error(ArgumentError) end end end letter_opener-1.10.0/letter_opener.gemspec0000644000004100000410000000223014620142103020672 0ustar www-datawww-dataGem::Specification.new do |s| s.name = "letter_opener" s.version = "1.10.0" s.author = "Ryan Bates" s.email = "ryan@railscasts.com" s.homepage = "https://github.com/ryanb/letter_opener" s.summary = "Preview mail in browser instead of sending." s.description = "When mail is sent from your application, Letter Opener will open a preview in the browser instead of sending." s.license = "MIT" s.files = Dir["{lib,spec}/**/*", "[A-Z]*"] - ["Gemfile.lock"] s.require_path = "lib" s.add_dependency 'launchy', '>= 2.2', '< 4' s.add_development_dependency 'rspec', '~> 3.10.0' s.add_development_dependency 'mail', '~> 2.6.0' s.required_rubygems_version = ">= 1.3.4" if s.respond_to?(:metadata) s.metadata = { 'bug_tracker_uri' => 'https://github.com/ryanb/letter_opener/issues', 'changelog_uri' => 'https://github.com/ryanb/letter_opener/blob/master/CHANGELOG.md', 'documentation_uri' => 'http://www.rubydoc.info/gems/letter_opener/', 'homepage_uri' => 'https://github.com/ryanb/letter_opener', 'source_code_uri' => 'https://github.com/ryanb/letter_opener/', } end end letter_opener-1.10.0/Rakefile0000644000004100000410000000026414620142103016130 0ustar www-datawww-datarequire 'bundler' Bundler::GemHelper.install_tasks require 'rspec/core/rake_task' desc "Run RSpec" RSpec::Core::RakeTask.new do |t| t.verbose = false end task :default => :spec letter_opener-1.10.0/Gemfile0000644000004100000410000000032614620142103015755 0ustar www-datawww-datasource "https://rubygems.org" gemspec if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("3.1") gem "net-imap", require: false gem "net-pop", require: false gem "net-smtp", require: false end gem "rake" letter_opener-1.10.0/LICENSE0000644000004100000410000000203614620142103015467 0ustar www-datawww-dataCopyright (c) 2012 Ryan Bates Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. letter_opener-1.10.0/README.md0000644000004100000410000001035214620142103015741 0ustar www-datawww-data# Letter Opener [![Ruby](https://github.com/ryanb/letter_opener/actions/workflows/ruby.yml/badge.svg)](https://github.com/ryanb/letter_opener/actions/workflows/ruby.yml) Preview email in the default browser instead of sending it. This means you do not need to set up email delivery in your development environment, and you no longer need to worry about accidentally sending a test email to someone else's address. ## Rails Setup First add the gem to your development environment and run the `bundle` command to install it. ```rb gem "letter_opener", group: :development ``` Then set the delivery method in `config/environments/development.rb` ```rb config.action_mailer.delivery_method = :letter_opener config.action_mailer.perform_deliveries = true ``` Now any email will pop up in your browser instead of being sent. The messages are stored in `tmp/letter_opener`. If you want to change application that will be used to open your emails you should override `LAUNCHY_APPLICATION` environment variable or set `Launchy.application` in the initializer. ### Configuration ```rb LetterOpener.configure do |config| # To overrider the location for message storage. # Default value is `tmp/letter_opener` config.location = Rails.root.join('tmp', 'my_mails') # To render only the message body, without any metadata or extra containers or styling. # Default value is `:default` that renders styled message with showing useful metadata. config.message_template = :light # To change default file URI scheme you can provide `file_uri_scheme` config. # It might be useful when you use WSL (Windows Subsystem for Linux) and default # scheme doesn't work for you. # Default value is blank config.file_uri_scheme = 'file://///wsl$/Ubuntu-18.04' end ``` ## Non Rails Setup If you aren't using Rails, this can be easily set up with the Mail gem. Just set the delivery method when configuring Mail and specify a location. ```rb require "letter_opener" Mail.defaults do delivery_method LetterOpener::DeliveryMethod, location: File.expand_path('../tmp/letter_opener', __FILE__) end ``` The method is similar if you're using the Pony gem: ```rb require "letter_opener" Pony.options = { via: LetterOpener::DeliveryMethod, via_options: {location: File.expand_path('../tmp/letter_opener', __FILE__)} } ``` Alternatively, if you are using ActionMailer directly (without Rails) you will need to add the delivery method. ```rb require "letter_opener" ActionMailer::Base.add_delivery_method :letter_opener, LetterOpener::DeliveryMethod, :location => File.expand_path('../tmp/letter_opener', __FILE__) ActionMailer::Base.delivery_method = :letter_opener ``` ## Remote Alternatives Letter Opener uses [Launchy](https://github.com/copiousfreetime/launchy) to open sent mail in the browser. This assumes the Ruby process is running on the local development machine. If you are using a separate staging server or VM this will not work. In that case consider using [Mailtrap](http://mailtrap.io/) or [MailCatcher](http://mailcatcher.me/). If you are running your application within a Docker Container or VM and do not have a browser available to open emails received by Letter Opener, you may see the following error: ``` WARN: Launchy::CommandNotFoundError: Unable to find a browser command. If this is unexpected, Please rerun with environment variable LAUNCHY_DEBUG=true or the '-d' commandline option and file a bug at https://github.com/copiousfreetime/launchy/issues/new ``` To resolve this, simply set the following ENV variables: ``` LAUNCHY_DRY_RUN=true BROWSER=/dev/null ``` In order to keep this project simple, I don't have plans to turn it into a Rails engine with an interface for browsing the sent mail but there is a [gem you can use for that](https://github.com/fgrehm/letter_opener_web). ## Development & Feedback Questions or problems? Please use the [issue tracker](https://github.com/ryanb/letter_opener/issues). If you would like to contribute to this project, fork this repository and run `bundle` and `rake` to run the tests. Pull requests appreciated. Special thanks to the [mail_view](https://github.com/37signals/mail_view/) gem for inspiring this project and for their mail template. Also thanks to [Vasiliy Ermolovich](https://github.com/nashby) for helping manage this project. letter_opener-1.10.0/CHANGELOG.md0000644000004100000410000001213514620142103016274 0ustar www-datawww-data## 1.10.0 ## * Allow Launchy 3.0+. ## 1.9.0 ## * Store mail location in `Mail::Message` object (thanks [Jonathan Chan](https://github.com/jonmchan)) * Drop Ruby 2 support. Support only Ruby 3.0+ * Remove `nkf` gem dependency. ## 1.8.1 ## * Fix duplication of Rails tasks caused by LetterOpener's rake task file. (thanks [zarqman](https://github.com/zarqman)) ## 1.8.0 ## * Allow configuration of the 'file///' part in Launchy.open path (thanks [ Regis Millet](https://github.com/Kulgar)) * Enhance 'tmp:clear' task to delete 'tmp/letter_opener' files (thanks [Joaquín Vicente](https://github.com/wacko)) * Convert mail's subject to UTF-8. (thanks [kuroponzu](https://github.com/kuroponzu)) * Use proper attachment's filename sanitization so we don't escape Japanese characters. ## 1.7.0 ## * Use default configuration in `Message::rendered_messages` (thanks [Krystan HuffMenne ](https://github.com/gitKrystan)) * Do not use `Rails.root` path if LetterOpener is used outside of Rails (thanks [centrevillage](https://github.com/centrevillage)) * Allow to set only `Mail#cc`/`Mail#bcc` without `Mail#to`. ## 1.6.0 ## * Do not depend on Mail gem to check delivery params. * Do not parse and escape url before passing it to Launchy. ## 1.5.0 ## * Use proper check for `Rails::Railties` (thanks [Florian Weingarten](https://github.com/fw42)) * Add a shim for the iFrame "srcdoc" attribute (make it work with IE). * Do not convert `-` to `_` in attachment file names. (thanks [Steven Harman](https://github.com/stevenharman)) * Drop Ruby 1.9 support. * Escape inline attachment names the same way they are stored in the attachments directory (thanks [Daniel Rikowski](https://github.com/daniel-rikowski)) * Increase timestamp precision in the mail filename. (thanks [Piotr Usewicz](https://github.com/pusewicz)) * Add ability to configure location of stored mails. (thanks [Ben](https://github.com/beejamin)) * Add light version of template for mails that doesn't render any additional styling. (thanks [Ben](https://github.com/beejamin)) ## 1.4.1 ## * Stop base tag appearing in plain-text previews. (thanks [Coby Chapple](https://github.com/cobyism)) ## 1.4.0 ## * Add base tag to the iframe so links work with X-Frame-Options set to SAMEORIGIN. (thanks [Jason Tokoph](https://github.com/jtokoph)) * Check delivery params before rendering an email to match SMTP behaviour. ## 1.3.0 ## * Fix message body encoding is observed correctly in QP CTE. (thanks [Mark Dodwell](https://github.com/mkdynamic)) * Remove fixed width on the mail content. (thanks [weexpectedTHIS](https://github.com/weexpectedTHIS)) * Render email content in the iframe. Fixes [#98](https://github.com/ryanb/letter_opener/issues/98). (thanks [Jacob Maine](https://github.com/mainej)) ## 1.2.0 ## * Fix auto_link() which in some cases would return an empty tag for plain text messages. (thanks [Kevin McPhillips](https://github.com/kmcphillips)) * Update styles. (thanks [Adam Doppelt](https://github.com/gurgeous)) ## 1.1.2 ## * Show formatted display names in html template (thanks [ClaireMcGinty](https://github.com/ClaireMcGinty)) * Use `file:///` uri scheme to fix Launchy on Windows. ## 1.1.1 ## * Handle cc and bcc as array of emails. (thanks [jordandcarter](https://github.com/jordandcarter)) * Use `file://` uri scheme since Launcy can't open escaped URL without it. (thanks [Adrian2112](https://github.com/Adrian2112)) * Update Launchy dependency to `~> 2.2` (thanks [JeanMertz](https://github.com/JeanMertz)) * Change all nonword chars in filename of attachment to underscore so it can be saved on all platforms. (thanks [phallstrom](https://github.com/phallstrom)) ## 1.1.0 ## * Update Launchy dependency to `~> 2.2.0` (thanks [webdevotion](https://github.com/webdevotion)) * Handle `sender` field (thanks [sjtipton](https://github.com/sjtipton)) * Show subject only if it's present (thanks [jadehyper](https://github.com/jadehyper)) * Show subject as title of web page (thanks [statique](https://github.com/statique)) ## 1.0.0 ## * Attachment Support (thanks [David Cornu](https://github.com/davidcornu)) * Escape HTML in subject and other fields * Raise an exception if the :location option is not present instead of using a default * Open rich version by default (thanks [Damir](https://github.com/sidonath)) * Override margin on dt and dd elements in CSS (thanks [Edgars Beigarts](https://github.com/ebeigarts)) * Autolink URLs in plain version (thanks [Matt Burke](https://github.com/spraints)) ## 0.1.0 ## * From and To show name and Email when specified * Fix bug when letter_opener couldn't open email in Windows * Handle spaces in the application path (thanks [Mike Boone](https://github.com/boone)) * As letter_opener doesn't work with Launchy < 2.0.4 let's depend on >= 2.0.4 (thanks [Samnang Chhun](https://github.com/samnang)) * Handle `reply_to` field (thanks [Wes Gibbs](https://github.com/wgibbs)) * Set the charset in email preview (thanks [Bruno Michel](https://github.com/nono)) ## 0.0.2 ## * Fixing launchy requirement (thanks [Bruno Michel](https://github.com/nono)) ## 0.0.1 ## * Initial release