Commit f0bdf7f8 authored by Robert Speicher's avatar Robert Speicher

Merge branch 'reply-by-email' into 'master'

Reply by email

Fixes #1360.

It's far from done, but _it works_.

See merge request !1173
parents 0daa21ed 15fc7bd6
...@@ -25,6 +25,7 @@ config/initializers/rack_attack.rb ...@@ -25,6 +25,7 @@ config/initializers/rack_attack.rb
config/initializers/smtp_settings.rb config/initializers/smtp_settings.rb
config/resque.yml config/resque.yml
config/unicorn.rb config/unicorn.rb
config/mail_room.yml
coverage/* coverage/*
db/*.sqlite3 db/*.sqlite3
db/*.sqlite3-journal db/*.sqlite3-journal
......
...@@ -272,3 +272,7 @@ end ...@@ -272,3 +272,7 @@ end
gem "newrelic_rpm" gem "newrelic_rpm"
gem 'octokit', '3.7.0' gem 'octokit', '3.7.0'
gem "mail_room", "~> 0.4.0"
gem 'email_reply_parser'
...@@ -156,6 +156,7 @@ GEM ...@@ -156,6 +156,7 @@ GEM
dotenv (0.9.0) dotenv (0.9.0)
dropzonejs-rails (0.7.1) dropzonejs-rails (0.7.1)
rails (> 3.1) rails (> 3.1)
email_reply_parser (0.5.8)
email_spec (1.6.0) email_spec (1.6.0)
launchy (~> 2.1) launchy (~> 2.1)
mail (~> 2.2) mail (~> 2.2)
...@@ -371,6 +372,7 @@ GEM ...@@ -371,6 +372,7 @@ GEM
systemu (~> 2.6.2) systemu (~> 2.6.2)
mail (2.6.3) mail (2.6.3)
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
mail_room (0.4.0)
method_source (0.8.2) method_source (0.8.2)
mime-types (1.25.1) mime-types (1.25.1)
mimemagic (0.3.0) mimemagic (0.3.0)
...@@ -773,6 +775,7 @@ DEPENDENCIES ...@@ -773,6 +775,7 @@ DEPENDENCIES
diffy (~> 3.0.3) diffy (~> 3.0.3)
doorkeeper (= 2.1.3) doorkeeper (= 2.1.3)
dropzonejs-rails dropzonejs-rails
email_reply_parser
email_spec (~> 1.6.0) email_spec (~> 1.6.0)
enumerize enumerize
factory_girl_rails factory_girl_rails
...@@ -805,6 +808,7 @@ DEPENDENCIES ...@@ -805,6 +808,7 @@ DEPENDENCIES
jquery-ui-rails jquery-ui-rails
kaminari (~> 0.15.1) kaminari (~> 0.15.1)
letter_opener letter_opener
mail_room (~> 0.4.0)
minitest (~> 5.3.0) minitest (~> 5.3.0)
mousetrap-rails mousetrap-rails
mysql2 mysql2
......
web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"} web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"}
worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q common -q default worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q common -q default
# mail_room: bundle exec mail_room -q -c config/mail_room.yml
...@@ -20,7 +20,7 @@ module IconsHelper ...@@ -20,7 +20,7 @@ module IconsHelper
end end
def boolean_to_icon(value) def boolean_to_icon(value)
if value.to_s == "true" if value
icon('circle', class: 'cgreen') icon('circle', class: 'cgreen')
else else
icon('power-off', class: 'clgray') icon('power-off', class: 'clgray')
......
class BaseMailer < ActionMailer::Base
add_template_helper ApplicationHelper
add_template_helper GitlabMarkdownHelper
attr_accessor :current_user
helper_method :current_user, :can?
default from: Proc.new { default_sender_address.format }
default reply_to: Proc.new { default_reply_to_address.format }
def self.delay
delay_for(2.seconds)
end
def can?
Ability.abilities.allowed?(current_user, action, subject)
end
private
def default_sender_address
address = Mail::Address.new(Gitlab.config.gitlab.email_from)
address.display_name = Gitlab.config.gitlab.email_display_name
address
end
def default_reply_to_address
address = Mail::Address.new(Gitlab.config.gitlab.email_reply_to)
address.display_name = Gitlab.config.gitlab.email_display_name
address
end
end
class EmailRejectionMailer < BaseMailer
def rejection(reason, original_raw, can_retry = false)
@reason = reason
@original_message = Mail::Message.new(original_raw)
headers = {
to: @original_message.from,
subject: "[Rejected] #{@original_message.subject}"
}
headers['Message-ID'] = SecureRandom.hex
headers['In-Reply-To'] = @original_message.message_id
headers['References'] = @original_message.message_id
headers['Reply-To'] = @original_message.to.first if can_retry
mail(headers)
end
end
...@@ -8,6 +8,8 @@ module Emails ...@@ -8,6 +8,8 @@ module Emails
from: sender(@issue.author_id), from: sender(@issue.author_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})")) subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key)
end end
def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id) def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id)
...@@ -19,6 +21,8 @@ module Emails ...@@ -19,6 +21,8 @@ module Emails
from: sender(updated_by_user_id), from: sender(updated_by_user_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})")) subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key)
end end
def closed_issue_email(recipient_id, issue_id, updated_by_user_id) def closed_issue_email(recipient_id, issue_id, updated_by_user_id)
...@@ -30,6 +34,8 @@ module Emails ...@@ -30,6 +34,8 @@ module Emails
from: sender(updated_by_user_id), from: sender(updated_by_user_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})")) subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key)
end end
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
...@@ -42,6 +48,8 @@ module Emails ...@@ -42,6 +48,8 @@ module Emails
from: sender(updated_by_user_id), from: sender(updated_by_user_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})")) subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key)
end end
end end
end end
...@@ -10,6 +10,8 @@ module Emails ...@@ -10,6 +10,8 @@ module Emails
from: sender(@merge_request.author_id), from: sender(@merge_request.author_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
SentNotification.record(@merge_request, recipient_id, reply_key)
end end
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id) def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id)
...@@ -23,6 +25,8 @@ module Emails ...@@ -23,6 +25,8 @@ module Emails
from: sender(updated_by_user_id), from: sender(updated_by_user_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
SentNotification.record(@merge_request, recipient_id, reply_key)
end end
def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
...@@ -36,6 +40,8 @@ module Emails ...@@ -36,6 +40,8 @@ module Emails
from: sender(updated_by_user_id), from: sender(updated_by_user_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
SentNotification.record(@merge_request, recipient_id, reply_key)
end end
def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
...@@ -48,6 +54,8 @@ module Emails ...@@ -48,6 +54,8 @@ module Emails
from: sender(updated_by_user_id), from: sender(updated_by_user_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
SentNotification.record(@merge_request, recipient_id, reply_key)
end end
def merge_request_status_email(recipient_id, merge_request_id, status, updated_by_user_id) def merge_request_status_email(recipient_id, merge_request_id, status, updated_by_user_id)
...@@ -58,52 +66,12 @@ module Emails ...@@ -58,52 +66,12 @@ module Emails
@target_url = namespace_project_merge_request_url(@project.namespace, @target_url = namespace_project_merge_request_url(@project.namespace,
@project, @project,
@merge_request) @merge_request)
set_reference("merge_request_#{merge_request_id}")
mail_answer_thread(@merge_request, mail_answer_thread(@merge_request,
from: sender(updated_by_user_id), from: sender(updated_by_user_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid}) #{@mr_status}")) subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
end
end
# Over rides default behaviour to show source/target SentNotification.record(@merge_request, recipient_id, reply_key)
# Formats arguments into a String suitable for use as an email subject
#
# extra - Extra Strings to be inserted into the subject
#
# Examples
#
# >> subject('Lorem ipsum')
# => "GitLab Merge Request | Lorem ipsum"
#
# # Automatically inserts Project name:
# Forked MR
# => source project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
# => target project => <Project id: 2, name: "My Ror", path: "ruby_on_rails", ...>
# => source branch => source
# => target branch => target
# >> subject('Lorem ipsum')
# => "GitLab Merge Request | Ruby on Rails:source >> My Ror:target | Lorem ipsum "
#
# Non Forked MR
# => source project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
# => target project => <Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
# => source branch => source
# => target branch => target
# >> subject('Lorem ipsum')
# => "GitLab Merge Request | Ruby on Rails | source >> target | Lorem ipsum "
# # Accepts multiple arguments
# >> subject('Lorem ipsum', 'Dolor sit amet')
# => "GitLab Merge Request | Lorem ipsum | Dolor sit amet"
def subject(*extra)
subject = "Merge Request | "
if @merge_request.for_fork?
subject << "#{@merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} >> #{@merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}"
else
subject << "#{@merge_request.source_project.name_with_namespace} | #{merge_request.source_branch} >> #{merge_request.target_branch}"
end end
subject << " | " + extra.join(' | ') if extra.present?
subject
end end
end end
...@@ -11,6 +11,8 @@ module Emails ...@@ -11,6 +11,8 @@ module Emails
from: sender(@note.author_id), from: sender(@note.author_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@commit.title} (#{@commit.short_id})")) subject: subject("#{@commit.title} (#{@commit.short_id})"))
SentNotification.record(@commit, recipient_id, reply_key)
end end
def note_issue_email(recipient_id, note_id) def note_issue_email(recipient_id, note_id)
...@@ -24,6 +26,8 @@ module Emails ...@@ -24,6 +26,8 @@ module Emails
from: sender(@note.author_id), from: sender(@note.author_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@issue.title} (##{@issue.iid})")) subject: subject("#{@issue.title} (##{@issue.iid})"))
SentNotification.record(@issue, recipient_id, reply_key)
end end
def note_merge_request_email(recipient_id, note_id) def note_merge_request_email(recipient_id, note_id)
...@@ -38,6 +42,8 @@ module Emails ...@@ -38,6 +42,8 @@ module Emails
from: sender(@note.author_id), from: sender(@note.author_id),
to: recipient(recipient_id), to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) subject: subject("#{@merge_request.title} (##{@merge_request.iid})"))
SentNotification.record(@merge_request, recipient_id, reply_key)
end end
end end
end end
class Notify < ActionMailer::Base class Notify < BaseMailer
include ActionDispatch::Routing::PolymorphicRoutes include ActionDispatch::Routing::PolymorphicRoutes
include Emails::Issues include Emails::Issues
...@@ -8,22 +8,9 @@ class Notify < ActionMailer::Base ...@@ -8,22 +8,9 @@ class Notify < ActionMailer::Base
include Emails::Profile include Emails::Profile
include Emails::Groups include Emails::Groups
add_template_helper ApplicationHelper
add_template_helper GitlabMarkdownHelper
add_template_helper MergeRequestsHelper add_template_helper MergeRequestsHelper
add_template_helper EmailsHelper add_template_helper EmailsHelper
attr_accessor :current_user
helper_method :current_user, :can?
default from: Proc.new { default_sender_address.format }
default reply_to: Gitlab.config.gitlab.email_reply_to
# Just send email with 2 seconds delay
def self.delay
delay_for(2.seconds)
end
def test_email(recipient_email, subject, body) def test_email(recipient_email, subject, body)
mail(to: recipient_email, mail(to: recipient_email,
subject: subject, subject: subject,
...@@ -48,13 +35,6 @@ class Notify < ActionMailer::Base ...@@ -48,13 +35,6 @@ class Notify < ActionMailer::Base
private private
# The default email address to send emails from
def default_sender_address
address = Mail::Address.new(Gitlab.config.gitlab.email_from)
address.display_name = Gitlab.config.gitlab.email_display_name
address
end
def can_send_from_user_email?(sender) def can_send_from_user_email?(sender)
sender_domain = sender.email.split("@").last sender_domain = sender.email.split("@").last
self.class.allowed_email_domains.include?(sender_domain) self.class.allowed_email_domains.include?(sender_domain)
...@@ -85,14 +65,6 @@ class Notify < ActionMailer::Base ...@@ -85,14 +65,6 @@ class Notify < ActionMailer::Base
@current_user.notification_email @current_user.notification_email
end end
# Set the References header field
#
# local_part - The local part of the referenced message ID
#
def set_reference(local_part)
headers["References"] = "<#{local_part}@#{Gitlab.config.gitlab.host}>"
end
# Formats arguments into a String suitable for use as an email subject # Formats arguments into a String suitable for use as an email subject
# #
# extra - Extra Strings to be inserted into the subject # extra - Extra Strings to be inserted into the subject
...@@ -126,14 +98,37 @@ class Notify < ActionMailer::Base ...@@ -126,14 +98,37 @@ class Notify < ActionMailer::Base
"<#{model_name}_#{model.id}@#{Gitlab.config.gitlab.host}>" "<#{model_name}_#{model.id}@#{Gitlab.config.gitlab.host}>"
end end
def mail_thread(model, headers = {})
if @project
headers['X-GitLab-Project'] = @project.name
headers['X-GitLab-Project-Id'] = @project.id
headers['X-GitLab-Project-Path'] = @project.path_with_namespace
end
headers["X-GitLab-#{model.class.name}-ID"] = model.id
if reply_key
headers['X-GitLab-Reply-Key'] = reply_key
address = Mail::Address.new(Gitlab::ReplyByEmail.reply_address(reply_key))
address.display_name = @project.name_with_namespace
headers['Reply-To'] = address
@reply_by_email = true
end
mail(headers)
end
# Send an email that starts a new conversation thread, # Send an email that starts a new conversation thread,
# with headers suitable for grouping by thread in email clients. # with headers suitable for grouping by thread in email clients.
# #
# See: mail_answer_thread # See: mail_answer_thread
def mail_new_thread(model, headers = {}, &block) def mail_new_thread(model, headers = {})
headers['Message-ID'] = message_id(model) headers['Message-ID'] = message_id(model)
headers['X-GitLab-Project'] = "#{@project.name} | " if @project
mail(headers, &block) mail_thread(model, headers)
end end
# Send an email that responds to an existing conversation thread, # Send an email that responds to an existing conversation thread,
...@@ -144,19 +139,17 @@ class Notify < ActionMailer::Base ...@@ -144,19 +139,17 @@ class Notify < ActionMailer::Base
# * have a subject that begin by 'Re: ' # * have a subject that begin by 'Re: '
# * have a 'In-Reply-To' or 'References' header that references the original 'Message-ID' # * have a 'In-Reply-To' or 'References' header that references the original 'Message-ID'
# #
def mail_answer_thread(model, headers = {}, &block) def mail_answer_thread(model, headers = {})
headers['Message-ID'] = SecureRandom.hex
headers['In-Reply-To'] = message_id(model) headers['In-Reply-To'] = message_id(model)
headers['References'] = message_id(model) headers['References'] = message_id(model)
headers['X-GitLab-Project'] = "#{@project.name} | " if @project
if headers[:subject] headers[:subject].prepend('Re: ') if headers[:subject]
headers[:subject].prepend('Re: ')
end
mail(headers, &block) mail_thread(model, headers)
end end
def can? def reply_key
Ability.abilities.allowed?(user, action, subject) @reply_key ||= Gitlab::ReplyByEmail.reply_key
end end
end end
class SentNotification < ActiveRecord::Base
belongs_to :project
belongs_to :noteable, polymorphic: true
belongs_to :recipient, class_name: "User"
validate :project, :recipient, :reply_key, presence: true
validate :reply_key, uniqueness: true
validates :noteable_id, presence: true, unless: :for_commit?
validates :commit_id, presence: true, if: :for_commit?
class << self
def for(reply_key)
find_by(reply_key: reply_key)
end
def record(noteable, recipient_id, reply_key)
return unless reply_key
noteable_id = nil
commit_id = nil
if noteable.is_a?(Commit)
commit_id = noteable.id
else
noteable_id = noteable.id
end
create(
project: noteable.project,
noteable_type: noteable.class.name,
noteable_id: noteable_id,
commit_id: commit_id,
recipient_id: recipient_id,
reply_key: reply_key
)
end
end
def for_commit?
noteable_type == "Commit"
end
def noteable
if for_commit?
project.commit(commit_id) rescue nil
else
super
end
end
end
...@@ -13,9 +13,9 @@ module Projects ...@@ -13,9 +13,9 @@ module Projects
filename = uploader.image? ? uploader.file.basename : uploader.file.filename filename = uploader.image? ? uploader.file.basename : uploader.file.filename
{ {
'alt' => filename, alt: filename,
'url' => uploader.secure_url, url: uploader.secure_url,
'is_image' => uploader.image? is_image: uploader.image?
} }
end end
......
...@@ -55,6 +55,10 @@ ...@@ -55,6 +55,10 @@
OmniAuth OmniAuth
%span.light.pull-right %span.light.pull-right
= boolean_to_icon Gitlab.config.omniauth.enabled = boolean_to_icon Gitlab.config.omniauth.enabled
%p
Reply by email
%span.light.pull-right
= boolean_to_icon Gitlab::ReplyByEmail.enabled?
.col-md-4 .col-md-4
%h4 %h4
Components Components
......
%p
Unfortunately, your email message to GitLab could not be processed.
= markdown @reason
Unfortunately, your email message to GitLab could not be processed.
= @reason
...@@ -36,6 +36,10 @@ ...@@ -36,6 +36,10 @@
&mdash; &mdash;
%br %br
- if @target_url - if @target_url
- if @reply_by_email
Reply to this email directly or
#{link_to "view it on GitLab", @target_url}.
- else
#{link_to "View it on GitLab", @target_url} #{link_to "View it on GitLab", @target_url}
= email_action @target_url = email_action @target_url
- if @project && !@disable_footer - if @project && !@disable_footer
......
class EmailReceiverWorker
include Sidekiq::Worker
sidekiq_options queue: :incoming_email
def perform(raw)
return unless Gitlab::ReplyByEmail.enabled?
begin
Gitlab::Email::Receiver.new(raw).execute
rescue => e
handle_failure(raw, e)
end
end
private
def handle_failure(raw, e)
Rails.logger.warn("Email can not be processed: #{e}\n\n#{raw}")
can_retry = false
reason = nil
case e
when Gitlab::Email::Receiver::SentNotificationNotFoundError
reason = "We couldn't figure out what the email is in reply to. Please create your comment through the web interface."
when Gitlab::Email::Receiver::EmptyEmailError
can_retry = true
reason = "It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies."
when Gitlab::Email::Receiver::AutoGeneratedEmailError
reason = "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface."
when Gitlab::Email::Receiver::UserNotFoundError
reason = "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface."
when Gitlab::Email::Receiver::UserBlockedError
reason = "Your account has been blocked. If you believe this is in error, contact a staff member."
when Gitlab::Email::Receiver::UserNotAuthorizedError
reason = "You are not allowed to respond to the thread you are replying to. If you believe this is in error, contact a staff member."
when Gitlab::Email::Receiver::NoteableNotFoundError
reason = "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member."
when Gitlab::Email::Receiver::InvalidNoteError
can_retry = true
reason = e.message
else
return
end
EmailRejectionMailer.delay.rejection(reason, raw, can_retry)
end
end
...@@ -37,7 +37,7 @@ start_no_deamonize() ...@@ -37,7 +37,7 @@ start_no_deamonize()
start_sidekiq() start_sidekiq()
{ {
bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1 bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1
} }
load_ok() load_ok()
......
#!/bin/sh
cd $(dirname $0)/..
app_root=$(pwd)
mail_room_pidfile="$app_root/tmp/pids/mail_room.pid"
mail_room_logfile="$app_root/log/mail_room.log"
mail_room_config="$app_root/config/mail_room.yml"
get_mail_room_pid()
{
local pid=$(cat $mail_room_pidfile)
if [ -z "$pid" ] ; then
echo "Could not find a PID in $mail_room_pidfile"
exit 1
fi
mail_room_pid=$pid
}
start()
{
bundle exec mail_room -q -c $mail_room_config >> $mail_room_logfile 2>&1 &
PID=$!
echo $PID > $mail_room_pidfile
}
stop()
{
get_mail_room_pid
kill -TERM $mail_room_pid
}
restart()
{
stop
start
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
*)
echo "Usage: $0 {start|stop|restart}"
;;
esac
...@@ -94,6 +94,13 @@ production: &base ...@@ -94,6 +94,13 @@ production: &base
# The default is 'tmp/repositories' relative to the root of the Rails app. # The default is 'tmp/repositories' relative to the root of the Rails app.
# repository_downloads_path: tmp/repositories # repository_downloads_path: tmp/repositories
## Reply by email
# Allow users to comment on issues and merge requests by replying to notification emails.
# For documentation on how to set this up, see http://doc.gitlab.com/ce/reply_by_email/README.md
reply_by_email:
enabled: false
address: "replies+%{reply_key}@gitlab.example.com"
## Gravatar ## Gravatar
## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html
gravatar: gravatar:
......
...@@ -150,6 +150,12 @@ Settings.gitlab['repository_downloads_path'] = File.absolute_path(Settings.gitla ...@@ -150,6 +150,12 @@ Settings.gitlab['repository_downloads_path'] = File.absolute_path(Settings.gitla
Settings.gitlab['restricted_signup_domains'] ||= [] Settings.gitlab['restricted_signup_domains'] ||= []
Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','git'] Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','git']
#
# Reply by email
#
Settings['reply_by_email'] ||= Settingslogic.new({})
Settings.reply_by_email['enabled'] = false if Settings.reply_by_email['enabled'].nil?
# #
# Gravatar # Gravatar
# #
......
:mailboxes:
-
# # IMAP server host
# :host: "imap.gmail.com"
# # IMAP server port
# :port: 993
# # Whether the IMAP server uses SSL
# :ssl: true
# # Email account username. Usually the full email address.
# :email: "replies@gitlab.example.com"
# # Email account password
# :password: "password"
# # The name of the mailbox where incoming mail will end up. Usually "inbox".
# :name: "inbox"
# # Always "sidekiq".
# :delivery_method: sidekiq
# :delivery_options:
# # The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml.
# :redis_url: redis://localhost:6379
# # Always "resque:gitlab".
# :namespace: resque:gitlab
# # Always "incoming_email".
# :queue: incoming_email
# # Always "EmailReceiverWorker"
# :worker: EmailReceiverWorker
class AddSentNotifications < ActiveRecord::Migration
def change
create_table :sent_notifications do |t|
t.references :project
t.references :noteable, polymorphic: true
t.references :recipient
t.string :commit_id
t.string :reply_key, null: false
end
add_index :sent_notifications, :reply_key, unique: true
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150812080800) do ActiveRecord::Schema.define(version: 20150818213832) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -405,6 +405,17 @@ ActiveRecord::Schema.define(version: 20150812080800) do ...@@ -405,6 +405,17 @@ ActiveRecord::Schema.define(version: 20150812080800) do
add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
create_table "sent_notifications", force: true do |t|
t.integer "project_id"
t.integer "noteable_id"
t.string "noteable_type"
t.integer "recipient_id"
t.string "commit_id"
t.string "reply_key", null: false
end
add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree
create_table "services", force: true do |t| create_table "services", force: true do |t|
t.string "type" t.string "type"
t.string "title" t.string "title"
......
...@@ -29,6 +29,7 @@ ...@@ -29,6 +29,7 @@
- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
- [Update](update/README.md) Update guides to upgrade your installation. - [Update](update/README.md) Update guides to upgrade your installation.
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page. - [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
- [Reply by email](reply_by_email/README.md) Allow users to comment on issues and merge requests by replying to notification emails.
## Contributor documentation ## Contributor documentation
......
...@@ -366,7 +366,7 @@ Make sure to edit the config file to match your setup: ...@@ -366,7 +366,7 @@ Make sure to edit the config file to match your setup:
# domain name of your host serving GitLab. # domain name of your host serving GitLab.
# If using Ubuntu default nginx install: # If using Ubuntu default nginx install:
# either remove the default_server from the listen line # either remove the default_server from the listen line
# or else rm -f /etc/sites-enabled/default # or else sudo rm -f /etc/nginx/sites-enabled/default
sudo editor /etc/nginx/sites-available/gitlab sudo editor /etc/nginx/sites-available/gitlab
**Note:** If you want to use HTTPS, replace the `gitlab` Nginx config with `gitlab-ssl`. See [Using HTTPS](#using-https) for HTTPS configuration details. **Note:** If you want to use HTTPS, replace the `gitlab` Nginx config with `gitlab-ssl`. See [Using HTTPS](#using-https) for HTTPS configuration details.
......
# Reply by email
GitLab can be set up to allow users to comment on issues and merge requests by replying to notification emails.
In order to do this, you need access to an IMAP-enabled email account, with a provider or server that supports [email sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing). Sub-addressing is a feature where any email to `user+some_arbitrary_tag@example.com` will end up in the mailbox for `user@example.com`, and is supported by providers such as Gmail, Yahoo! Mail, Outlook.com and iCloud, as well as the [Postfix](http://www.postfix.org/) mail server which you can run on-premises.
## Set it up
In this example, we'll use the Gmail address `gitlab-replies@gmail.com`. If you're actually using Gmail with Reply by email, make sure you have [IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) and [allow less secure apps to access the account](https://support.google.com/accounts/answer/6010255).
### Installations from source
1. Go to the GitLab installation directory:
```sh
cd /home/git/gitlab
```
1. Find the `reply_by_email` section in `config/gitlab.yml`, enable the feature and enter the email address including a placeholder for the `reply_key`:
```sh
sudo editor config/gitlab.yml
```
```yaml
reply_by_email:
enabled: true
address: "gitlab-replies+%{reply_key}@gmail.com"
```
As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-replies@gmail.com`.
2. Find `config/mail_room.yml.example` and copy it to `config/mail_room.yml`:
```sh
sudo cp config/mail_room.yml.example config/mail_room.yml
```
3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account:
```sh
sudo editor config/mail_room.yml
```
```yaml
:mailboxes:
-
# IMAP server host
:host: "imap.gmail.com"
# IMAP server port
:port: 993
# Whether the IMAP server uses SSL
:ssl: true
# Email account username. Usually the full email address.
:email: "gitlab-replies@gmail.com"
# Email account password
:password: "[REDACTED]"
# The name of the mailbox where incoming mail will end up. Usually "inbox".
:name: "inbox"
# Always "sidekiq".
:delivery_method: sidekiq
:delivery_options:
# The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml.
:redis_url: redis://localhost:6379
# Always "resque:gitlab".
:namespace: resque:gitlab
# Always "incoming_email".
:queue: incoming_email
# Always "EmailReceiverWorker"
:worker: EmailReceiverWorker
```
4. Find `lib/support/init.d/gitlab.default.example` and copy it to `/etc/default/gitlab`:
```sh
sudo cp lib/support/init.d/gitlab.default.example /etc/default/gitlab
```
5. Edit `/etc/default/gitlab` to enable `mail_room`:
```sh
sudo editor /etc/default/gitlab
```
```sh
mail_room_enabled=true
```
6. Restart GitLab:
```sh
sudo service gitlab restart
```
7. Check if everything is configured correctly:
```sh
sudo bundle exec rake gitlab:reply_by_email:check RAILS_ENV=production
```
8. Reply by email should now be working.
### Omnibus package installations
TODO
### Development
1. Go to the GitLab installation directory.
1. Find the `reply_by_email` section in `config/gitlab.yml`, enable the feature and enter the email address including a placeholder for the `reply_key`:
```yaml
reply_by_email:
enabled: true
address: "gitlab-replies+%{reply_key}@gmail.com"
```
As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-replies@gmail.com`.
2. Find `config/mail_room.yml.example` and copy it to `config/mail_room.yml`:
```sh
sudo cp config/mail_room.yml.example config/mail_room.yml
```
3. Uncomment the configuration options in `config/mail_room.yml` and fill in the details for your specific IMAP server and email account:
```yaml
:mailboxes:
-
# IMAP server host
:host: "imap.gmail.com"
# IMAP server port
:port: 993
# Whether the IMAP server uses SSL
:ssl: true
# Email account username. Usually the full email address.
:email: "gitlab-replies@gmail.com"
# Email account password
:password: "[REDACTED]"
# The name of the mailbox where incoming mail will end up. Usually "inbox".
:name: "inbox"
# Always "sidekiq".
:delivery_method: sidekiq
:delivery_options:
# The URL to the Redis server used by Sidekiq. Should match the URL in config/resque.yml.
:redis_url: redis://localhost:6379
# Always "resque:gitlab".
:namespace: resque:gitlab
# Always "incoming_email".
:queue: incoming_email
# Always "EmailReceiverWorker"
:worker: EmailReceiverWorker
```
4. Uncomment the `mail_room` line in your `Procfile`:
```yaml
mail_room: bundle exec mail_room -q -c config/mail_room.yml
```
6. Restart GitLab:
```sh
bundle exec foreman start
```
7. Check if everything is configured correctly:
```sh
bundle exec rake gitlab:reply_by_email:check RAILS_ENV=development
```
8. Reply by email should now be working.
module Gitlab
module Email
class AttachmentUploader
attr_accessor :message
def initialize(message)
@message = message
end
def execute(project)
attachments = []
message.attachments.each do |attachment|
tmp = Tempfile.new("gitlab-email-attachment")
begin
File.open(tmp.path, "w+b") { |f| f.write attachment.body.decoded }
file = {
tempfile: tmp,
filename: attachment.filename,
content_type: attachment.content_type
}
link = ::Projects::UploadService.new(project, file).execute
attachments << link if link
ensure
tmp.close!
end
end
attachments
end
end
end
end
# Inspired in great part by Discourse's Email::Receiver
module Gitlab
module Email
class Receiver
class ProcessingError < StandardError; end
class EmailUnparsableError < ProcessingError; end
class SentNotificationNotFoundError < ProcessingError; end
class EmptyEmailError < ProcessingError; end
class AutoGeneratedEmailError < ProcessingError; end
class UserNotFoundError < ProcessingError; end
class UserBlockedError < ProcessingError; end
class UserNotAuthorizedError < ProcessingError; end
class NoteableNotFoundError < ProcessingError; end
class InvalidNoteError < ProcessingError; end
def initialize(raw)
@raw = raw
end
def execute
raise EmptyEmailError if @raw.blank?
raise SentNotificationNotFoundError unless sent_notification
raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/
author = sent_notification.recipient
raise UserNotFoundError unless author
raise UserBlockedError if author.blocked?
project = sent_notification.project
raise UserNotAuthorizedError unless project && author.can?(:create_note, project)
raise NoteableNotFoundError unless sent_notification.noteable
reply = ReplyParser.new(message).execute.strip
raise EmptyEmailError if reply.blank?
reply = add_attachments(reply)
note = create_note(reply)
unless note.persisted?
message = "The comment could not be created for the following reasons:"
note.errors.full_messages.each do |error|
message << "\n\n- #{error}"
end
raise InvalidNoteError, message
end
end
private
def message
@message ||= Mail::Message.new(@raw)
rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e
raise EmailUnparsableError, e
end
def reply_key
reply_key = nil
message.to.each do |address|
reply_key = Gitlab::ReplyByEmail.reply_key_from_address(address)
break if reply_key
end
reply_key
end
def sent_notification
return nil unless reply_key
SentNotification.for(reply_key)
end
def add_attachments(reply)
attachments = Email::AttachmentUploader.new(message).execute(sent_notification.project)
attachments.each do |link|
text = "[#{link[:alt]}](#{link[:url]})"
text.prepend("!") if link[:is_image]
reply << "\n\n#{text}"
end
reply
end
def create_note(reply)
Notes::CreateService.new(
sent_notification.project,
sent_notification.recipient,
note: reply,
noteable_type: sent_notification.noteable_type,
noteable_id: sent_notification.noteable_id,
commit_id: sent_notification.commit_id
).execute
end
end
end
end
# Inspired in great part by Discourse's Email::Receiver
module Gitlab
module Email
class ReplyParser
attr_accessor :message
def initialize(message)
@message = message
end
def execute
body = select_body(message)
encoding = body.encoding
body = discourse_email_trimmer(body)
body = EmailReplyParser.parse_reply(body)
body.force_encoding(encoding).encode("UTF-8")
end
private
def select_body(message)
text = message.text_part if message.multipart?
text ||= message if message.content_type !~ /text\/html/
return "" unless text
text = fix_charset(text)
# Certain trigger phrases that means we didn't parse correctly
if text =~ /(Content\-Type\:|multipart\/alternative|text\/plain)/
return ""
end
text
end
# Force encoding to UTF-8 on a Mail::Message or Mail::Part
def fix_charset(object)
return nil if object.nil?
if object.charset
object.body.decoded.force_encoding(object.charset.gsub(/utf8/i, "UTF-8")).encode("UTF-8").to_s
else
object.body.to_s
end
rescue
nil
end
REPLYING_HEADER_LABELS = %w(From Sent To Subject Reply To Cc Bcc Date)
REPLYING_HEADER_REGEX = Regexp.union(REPLYING_HEADER_LABELS.map { |label| "#{label}:" })
def discourse_email_trimmer(body)
lines = body.scrub.lines.to_a
range_end = 0
lines.each_with_index do |l, idx|
# This one might be controversial but so many reply lines have years, times and end with a colon.
# Let's try it and see how well it works.
break if (l =~ /\d{4}/ && l =~ /\d:\d\d/ && l =~ /\:$/) ||
(l =~ /On \w+ \d+,? \d+,?.*wrote:/)
# Headers on subsequent lines
break if (0..2).all? { |off| lines[idx+off] =~ REPLYING_HEADER_REGEX }
# Headers on the same line
break if REPLYING_HEADER_LABELS.count { |label| l.include?(label) } >= 3
range_end = idx
end
lines[0..range_end].join.strip
end
end
end
end
module Gitlab
module ReplyByEmail
class << self
def enabled?
config.enabled && address_formatted_correctly?
end
def address_formatted_correctly?
config.address &&
config.address.include?("%{reply_key}")
end
def reply_key
return nil unless enabled?
SecureRandom.hex(16)
end
def reply_address(reply_key)
config.address.gsub('%{reply_key}', reply_key)
end
def reply_key_from_address(address)
regex = address_regex
return unless regex
match = address.match(regex)
return unless match
match[1]
end
private
def config
Gitlab.config.reply_by_email
end
def address_regex
wildcard_address = config.address
return nil unless wildcard_address
regex = Regexp.escape(wildcard_address)
regex = regex.gsub(Regexp.escape('%{reply_key}'), "(.+)")
Regexp.new(regex).freeze
end
end
end
end
...@@ -35,6 +35,8 @@ pid_path="$app_root/tmp/pids" ...@@ -35,6 +35,8 @@ pid_path="$app_root/tmp/pids"
socket_path="$app_root/tmp/sockets" socket_path="$app_root/tmp/sockets"
web_server_pid_path="$pid_path/unicorn.pid" web_server_pid_path="$pid_path/unicorn.pid"
sidekiq_pid_path="$pid_path/sidekiq.pid" sidekiq_pid_path="$pid_path/sidekiq.pid"
mail_room_enabled=false
mail_room_pid_path="$pid_path/mail_room.pid"
shell_path="/bin/bash" shell_path="/bin/bash"
# Read configuration variable file if it is present # Read configuration variable file if it is present
...@@ -70,13 +72,20 @@ check_pids(){ ...@@ -70,13 +72,20 @@ check_pids(){
else else
spid=0 spid=0
fi fi
if [ "$mail_room_enabled" = true ]; then
if [ -f "$mail_room_pid_path" ]; then
mpid=$(cat "$mail_room_pid_path")
else
mpid=0
fi
fi
} }
## Called when we have started the two processes and are waiting for their pid files. ## Called when we have started the two processes and are waiting for their pid files.
wait_for_pids(){ wait_for_pids(){
# We are sleeping a bit here mostly because sidekiq is slow at writing it's pid # We are sleeping a bit here mostly because sidekiq is slow at writing it's pid
i=0; i=0;
while [ ! -f $web_server_pid_path -o ! -f $sidekiq_pid_path ]; do while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ]; }; do
sleep 0.1; sleep 0.1;
i=$((i+1)) i=$((i+1))
if [ $((i%10)) = 0 ]; then if [ $((i%10)) = 0 ]; then
...@@ -111,7 +120,15 @@ check_status(){ ...@@ -111,7 +120,15 @@ check_status(){
else else
sidekiq_status="-1" sidekiq_status="-1"
fi fi
if [ $web_status = 0 -a $sidekiq_status = 0 ]; then if [ "$mail_room_enabled" = true ]; then
if [ $mpid -ne 0 ]; then
kill -0 "$mpid" 2>/dev/null
mail_room_status="$?"
else
mail_room_status="-1"
fi
fi
if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ]; }; then
gitlab_status=0 gitlab_status=0
else else
# http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html # http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
...@@ -125,26 +142,33 @@ check_stale_pids(){ ...@@ -125,26 +142,33 @@ check_stale_pids(){
check_status check_status
# If there is a pid it is something else than 0, the service is running if # If there is a pid it is something else than 0, the service is running if
# *_status is == 0. # *_status is == 0.
if [ "$wpid" != "0" -a "$web_status" != "0" ]; then if [ "$wpid" != "0" ] && [ "$web_status" != "0" ]; then
echo "Removing stale Unicorn web server pid. This is most likely caused by the web server crashing the last time it ran." echo "Removing stale Unicorn web server pid. This is most likely caused by the web server crashing the last time it ran."
if ! rm "$web_server_pid_path"; then if ! rm "$web_server_pid_path"; then
echo "Unable to remove stale pid, exiting." echo "Unable to remove stale pid, exiting."
exit 1 exit 1
fi fi
fi fi
if [ "$spid" != "0" -a "$sidekiq_status" != "0" ]; then if [ "$spid" != "0" ] && [ "$sidekiq_status" != "0" ]; then
echo "Removing stale Sidekiq job dispatcher pid. This is most likely caused by Sidekiq crashing the last time it ran." echo "Removing stale Sidekiq job dispatcher pid. This is most likely caused by Sidekiq crashing the last time it ran."
if ! rm "$sidekiq_pid_path"; then if ! rm "$sidekiq_pid_path"; then
echo "Unable to remove stale pid, exiting" echo "Unable to remove stale pid, exiting"
exit 1 exit 1
fi fi
fi fi
if [ "$mail_room_enabled" = true ] && [ "$mpid" != "0" ] && [ "$mail_room_status" != "0" ]; then
echo "Removing stale MailRoom job dispatcher pid. This is most likely caused by MailRoom crashing the last time it ran."
if ! rm "$mail_room_pid_path"; then
echo "Unable to remove stale pid, exiting"
exit 1
fi
fi
} }
## If no parts of the service is running, bail out. ## If no parts of the service is running, bail out.
exit_if_not_running(){ exit_if_not_running(){
check_stale_pids check_stale_pids
if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then
echo "GitLab is not running." echo "GitLab is not running."
exit exit
fi fi
...@@ -154,12 +178,14 @@ exit_if_not_running(){ ...@@ -154,12 +178,14 @@ exit_if_not_running(){
start_gitlab() { start_gitlab() {
check_stale_pids check_stale_pids
if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then if [ "$web_status" != "0" ]; then
echo -n "Starting both the GitLab Unicorn and Sidekiq" echo "Starting GitLab Unicorn"
elif [ "$web_status" != "0" ]; then fi
echo -n "Starting GitLab Unicorn" if [ "$sidekiq_status" != "0" ]; then
elif [ "$sidekiq_status" != "0" ]; then echo "Starting GitLab Sidekiq"
echo -n "Starting GitLab Sidekiq" fi
if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" != "0" ]; then
echo "Starting GitLab MailRoom"
fi fi
# Then check if the service is running. If it is: don't start again. # Then check if the service is running. If it is: don't start again.
...@@ -179,22 +205,33 @@ start_gitlab() { ...@@ -179,22 +205,33 @@ start_gitlab() {
RAILS_ENV=$RAILS_ENV bin/background_jobs start & RAILS_ENV=$RAILS_ENV bin/background_jobs start &
fi fi
if [ "$mail_room_enabled" = true ]; then
# If MailRoom is already running, don't start it again.
if [ "$mail_room_status" = "0" ]; then
echo "The MailRoom email processor is already running with pid $mpid, not restarting"
else
RAILS_ENV=$RAILS_ENV bin/mail_room start &
fi
fi
# Wait for the pids to be planted # Wait for the pids to be planted
wait_for_pids wait_for_pids
# Finally check the status to tell wether or not GitLab is running # Finally check the status to tell wether or not GitLab is running
print_status print_status
} }
## Asks the Unicorn and the Sidekiq if they would be so kind as to stop, if not kills them. ## Asks Unicorn, Sidekiq and MailRoom if they would be so kind as to stop, if not kills them.
stop_gitlab() { stop_gitlab() {
exit_if_not_running exit_if_not_running
if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then if [ "$web_status" = "0" ]; then
echo -n "Shutting down both Unicorn and Sidekiq" echo "Shutting down GitLab Unicorn"
elif [ "$web_status" = "0" ]; then fi
echo -n "Shutting down Unicorn" if [ "$sidekiq_status" = "0" ]; then
elif [ "$sidekiq_status" = "0" ]; then echo "Shutting down GitLab Sidekiq"
echo -n "Shutting down Sidekiq" fi
if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; then
echo "Shutting down GitLab MailRoom"
fi fi
# If the Unicorn web server is running, tell it to stop; # If the Unicorn web server is running, tell it to stop;
...@@ -205,13 +242,17 @@ stop_gitlab() { ...@@ -205,13 +242,17 @@ stop_gitlab() {
if [ "$sidekiq_status" = "0" ]; then if [ "$sidekiq_status" = "0" ]; then
RAILS_ENV=$RAILS_ENV bin/background_jobs stop RAILS_ENV=$RAILS_ENV bin/background_jobs stop
fi fi
# And do the same thing for the MailRoom.
if [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; then
RAILS_ENV=$RAILS_ENV bin/mail_room stop
fi
# If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script. # If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script.
while [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; do while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; }; do
sleep 1 sleep 1
check_status check_status
printf "." printf "."
if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then
printf "\n" printf "\n"
break break
fi fi
...@@ -220,7 +261,10 @@ stop_gitlab() { ...@@ -220,7 +261,10 @@ stop_gitlab() {
sleep 1 sleep 1
# Cleaning up unused pids # Cleaning up unused pids
rm "$web_server_pid_path" 2>/dev/null rm "$web_server_pid_path" 2>/dev/null
# rm "$sidekiq_pid_path" # Sidekiq seems to be cleaning up it's own pid. # rm "$sidekiq_pid_path" 2>/dev/null # Sidekiq seems to be cleaning up it's own pid.
if [ "$mail_room_enabled" = true ]; then
rm "$mail_room_pid_path" 2>/dev/null
fi
print_status print_status
} }
...@@ -228,7 +272,7 @@ stop_gitlab() { ...@@ -228,7 +272,7 @@ stop_gitlab() {
## Prints the status of GitLab and it's components. ## Prints the status of GitLab and it's components.
print_status() { print_status() {
check_status check_status
if [ "$web_status" != "0" -a "$sidekiq_status" != "0" ]; then if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; }; then
echo "GitLab is not running." echo "GitLab is not running."
return return
fi fi
...@@ -242,7 +286,14 @@ print_status() { ...@@ -242,7 +286,14 @@ print_status() {
else else
printf "The GitLab Sidekiq job dispatcher is \033[31mnot running\033[0m.\n" printf "The GitLab Sidekiq job dispatcher is \033[31mnot running\033[0m.\n"
fi fi
if [ "$web_status" = "0" -a "$sidekiq_status" = "0" ]; then if [ "$mail_room_enabled" = true ]; then
if [ "$mail_room_status" = "0" ]; then
echo "The GitLab MailRoom email processor with pid $spid is running."
else
printf "The GitLab MailRoom email processor is \033[31mnot running\033[0m.\n"
fi
fi
if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; }; then
printf "GitLab and all its components are \033[32mup and running\033[0m.\n" printf "GitLab and all its components are \033[32mup and running\033[0m.\n"
fi fi
} }
...@@ -257,9 +308,15 @@ reload_gitlab(){ ...@@ -257,9 +308,15 @@ reload_gitlab(){
printf "Reloading GitLab Unicorn configuration... " printf "Reloading GitLab Unicorn configuration... "
RAILS_ENV=$RAILS_ENV bin/web reload RAILS_ENV=$RAILS_ENV bin/web reload
echo "Done." echo "Done."
echo "Restarting GitLab Sidekiq since it isn't capable of reloading its config..." echo "Restarting GitLab Sidekiq since it isn't capable of reloading its config..."
RAILS_ENV=$RAILS_ENV bin/background_jobs restart RAILS_ENV=$RAILS_ENV bin/background_jobs restart
if [ "$mail_room_enabled" != true ]; then
echo "Restarting GitLab MailRoom since it isn't capable of reloading its config..."
RAILS_ENV=$RAILS_ENV bin/mail_room restart
fi
wait_for_pids wait_for_pids
print_status print_status
} }
...@@ -267,7 +324,7 @@ reload_gitlab(){ ...@@ -267,7 +324,7 @@ reload_gitlab(){
## Restarts Sidekiq and Unicorn. ## Restarts Sidekiq and Unicorn.
restart_gitlab(){ restart_gitlab(){
check_status check_status
if [ "$web_status" = "0" -o "$sidekiq_status" = "0" ]; then if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; }; then
stop_gitlab stop_gitlab
fi fi
start_gitlab start_gitlab
......
...@@ -30,6 +30,15 @@ web_server_pid_path="$pid_path/unicorn.pid" ...@@ -30,6 +30,15 @@ web_server_pid_path="$pid_path/unicorn.pid"
# The default is "$pid_path/sidekiq.pid" # The default is "$pid_path/sidekiq.pid"
sidekiq_pid_path="$pid_path/sidekiq.pid" sidekiq_pid_path="$pid_path/sidekiq.pid"
# mail_room_enabled specifies whether mail_room, which is used to process incoming email, is enabled.
# This is required for the Reply by email feature.
# The default is "false"
mail_room_enabled=false
# mail_room_pid_path defines the path in which to create the pid file for mail_room
# The default is "$pid_path/mail_room.pid"
mail_room_pid_path="$pid_path/mail_room.pid"
# shell_path defines the path of shell for "$app_user" in case you are using # shell_path defines the path of shell for "$app_user" in case you are using
# shell other than "bash" # shell other than "bash"
# The default is "/bin/bash" # The default is "/bin/bash"
......
...@@ -2,6 +2,7 @@ namespace :gitlab do ...@@ -2,6 +2,7 @@ namespace :gitlab do
desc "GitLab | Check the configuration of GitLab and its environment" desc "GitLab | Check the configuration of GitLab and its environment"
task check: %w{gitlab:gitlab_shell:check task check: %w{gitlab:gitlab_shell:check
gitlab:sidekiq:check gitlab:sidekiq:check
gitlab:reply_by_email:check
gitlab:ldap:check gitlab:ldap:check
gitlab:app:check} gitlab:app:check}
...@@ -629,6 +630,174 @@ namespace :gitlab do ...@@ -629,6 +630,174 @@ namespace :gitlab do
end end
end end
namespace :reply_by_email do
desc "GitLab | Check the configuration of Reply by email"
task check: :environment do
warn_user_is_not_gitlab
start_checking "Reply by email"
if Gitlab.config.reply_by_email.enabled
check_address_formatted_correctly
check_mail_room_config_exists
check_imap_authentication
if Rails.env.production?
check_initd_configured_correctly
check_mail_room_running
else
check_foreman_configured_correctly
end
else
puts 'Reply by email is disabled in config/gitlab.yml'
end
finished_checking "Reply by email"
end
# Checks
########################
def check_address_formatted_correctly
print "Address formatted correctly? ... "
if Gitlab::ReplyByEmail.address_formatted_correctly?
puts "yes".green
else
puts "no".red
try_fixing_it(
"Make sure that the address in config/gitlab.yml includes the '%{reply_key}' placeholder."
)
fix_and_rerun
end
end
def check_initd_configured_correctly
print "Init.d configured correctly? ... "
path = "/etc/default/gitlab"
if File.exist?(path) && File.read(path).include?("mail_room_enabled=true")
puts "yes".green
else
puts "no".red
try_fixing_it(
"Enable mail_room in the init.d configuration."
)
for_more_information(
"doc/reply_by_email/README.md"
)
fix_and_rerun
end
end
def check_foreman_configured_correctly
print "Foreman configured correctly? ... "
path = Rails.root.join("Procfile")
if File.exist?(path) && File.read(path) =~ /^mail_room:/
puts "yes".green
else
puts "no".red
try_fixing_it(
"Enable mail_room in your Procfile."
)
for_more_information(
"doc/reply_by_email/README.md"
)
fix_and_rerun
end
end
def check_mail_room_running
print "MailRoom running? ... "
path = "/etc/default/gitlab"
unless File.exist?(path) && File.read(path).include?("mail_room_enabled=true")
puts "can't check because of previous errors".magenta
return
end
if mail_room_running?
puts "yes".green
else
puts "no".red
try_fixing_it(
sudo_gitlab("RAILS_ENV=production bin/mail_room start")
)
for_more_information(
see_installation_guide_section("Install Init Script"),
"see log/mail_room.log for possible errors"
)
fix_and_rerun
end
end
def check_mail_room_config_exists
print "MailRoom config exists? ... "
mail_room_config_file = Rails.root.join("config", "mail_room.yml")
if File.exists?(mail_room_config_file)
puts "yes".green
else
puts "no".red
try_fixing_it(
"Copy config/mail_room.yml.example to config/mail_room.yml",
"Check that the information in config/mail_room.yml is correct"
)
for_more_information(
"doc/reply_by_email/README.md"
)
fix_and_rerun
end
end
def check_imap_authentication
print "IMAP server credentials are correct? ... "
mail_room_config_file = Rails.root.join("config", "mail_room.yml")
unless File.exists?(mail_room_config_file)
puts "can't check because of previous errors".magenta
return
end
config = YAML.load_file(mail_room_config_file)[:mailboxes].first rescue nil
if config
begin
imap = Net::IMAP.new(config[:host], port: config[:port], ssl: config[:ssl])
imap.login(config[:email], config[:password])
connected = true
rescue
connected = false
end
end
if connected
puts "yes".green
else
puts "no".red
try_fixing_it(
"Check that the information in config/mail_room.yml is correct"
)
for_more_information(
"doc/reply_by_email/README.md"
)
fix_and_rerun
end
end
def mail_room_running?
ps_ux, _ = Gitlab::Popen.popen(%W(ps ux))
ps_ux.include?("mail_room")
end
end
namespace :ldap do namespace :ldap do
task :check, [:limit] => :environment do |t, args| task :check, [:limit] => :environment do |t, args|
# Only show up to 100 results because LDAP directories can be very big. # Only show up to 100 results because LDAP directories can be very big.
......
Delivered-To: reply@discourse.org
Return-Path: <walter.white@googlemail.com>
MIME-Version: 1.0
In-Reply-To: <topic/22638/86406@meta.discourse.org>
References: <topic/22638@meta.discourse.org>
<topic/22638/86406@meta.discourse.org>
Date: Fri, 28 Nov 2014 12:53:21 -0800
Subject: Re: [Discourse Meta] [Lounge] Testing default email replies
From: Walter White <walter.white@googlemail.com>
To: Discourse Meta <reply@discourse.org>
Content-Type: multipart/alternative; boundary=089e0149cfa485c6630508f173df
--089e0149cfa485c6630508f173df
Content-Type: text/plain; charset=UTF-8
### this is a reply from Android 5 gmail
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown
fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
This is **bold** in Markdown.
This is a link to http://example.com
On Nov 28, 2014 12:36 PM, "Arpit Jalan" <info@discourse.org> wrote:
> techAPJ <https://meta.discourse.org/users/techapj>
> November 28
>
> Test reply.
>
> First paragraph.
>
> Second paragraph.
>
> To respond, reply to this email or visit
> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in
> your browser.
> ------------------------------
> Previous Replies codinghorror
> <https://meta.discourse.org/users/codinghorror>
> November 28
>
> We're testing the latest GitHub email processing library which we are
> integrating now.
>
> https://github.com/github/email_reply_parser
>
> Go ahead and reply to this topic and I'll reply from various email clients
> for testing.
> ------------------------------
>
> To respond, reply to this email or visit
> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in
> your browser.
>
> To unsubscribe from these emails, visit your user preferences
> <https://meta.discourse.org/my/preferences>.
>
--089e0149cfa485c6630508f173df
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
<p dir=3D"ltr">### this is a reply from Android 5 gmail</p>
<p dir=3D"ltr">The quick brown fox jumps over the lazy dog. The quick brown=
fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. =
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over=
the lazy dog. The quick brown fox jumps over the lazy dog. </p>
<p dir=3D"ltr">This is **bold** in Markdown.</p>
<p dir=3D"ltr">This is a link to <a href=3D"http://example.com">http://exam=
ple.com</a></p>
<div class=3D"gmail_quote">On Nov 28, 2014 12:36 PM, &quot;Arpit Jalan&quot=
; &lt;<a href=3D"mailto:info@discourse.org">info@discourse.org</a>&gt; wrot=
e:<br type=3D"attribution"><blockquote class=3D"gmail_quote" style=3D"margi=
n:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div>
<table style=3D"margin-bottom:25px" cellspacing=3D"0" cellpadding=3D"0" bor=
der=3D"0">
<tbody>
<tr>
<td style=3D"vertical-align:top;width:55px">
<img src=3D"https://meta-discourse.global.ssl.fastly.net/user_avata=
r/meta.discourse.org/techapj/45/3281.png" title=3D"techAPJ" style=3D"max-wi=
dth:100%" width=3D"45" height=3D"45">
</td>
<td>
<a href=3D"https://meta.discourse.org/users/techapj" style=3D"text-=
decoration:none;font-weight:bold;color:#006699;font-size:13px;font-family:&=
#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;color:#3b5998;text-d=
ecoration:none;font-weight:bold" target=3D"_blank">techAPJ</a><br>
<span style=3D"text-align:right;color:#999999;padding-right:5px;fon=
t-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:=
11px">November 28</span>
</td>
</tr>
<tr>
<td style=3D"padding-top:5px" colspan=3D"2">
<p style=3D"margin-top:0;border:0">Test reply.</p>
<p style=3D"margin-top:0;border:0">First paragraph.</p>
<p style=3D"margin-top:0;border:0">Second paragraph.</p>
</td>
</tr>
</tbody>
</table>
<div style=3D"color:#666">
<p>To respond, reply to this email or visit <a href=3D"https://meta.dis=
course.org/t/testing-default-email-replies/22638/3" style=3D"text-decoratio=
n:none;font-weight:bold;color:#006699;color:#666" target=3D"_blank">https:/=
/meta.discourse.org/t/testing-default-email-replies/22638/3</a> in your bro=
wser.</p>
</div>
<hr style=3D"background-color:#ddd;min-height:1px;border:1px;background-c=
olor:#ddd;min-height:1px;border:1px">
<h4>Previous Replies</h4>
<table style=3D"margin-bottom:25px" cellspacing=3D"0" cellpadding=3D"0" b=
order=3D"0">
<tbody>
<tr>
<td style=3D"vertical-align:top;width:55px">
<img src=3D"https://meta-discourse.global.ssl.fastly.net/user_avata=
r/meta.discourse.org/codinghorror/45/5297.png" title=3D"codinghorror" style=
=3D"max-width:100%" width=3D"45" height=3D"45">
</td>
<td>
<a href=3D"https://meta.discourse.org/users/codinghorror" style=3D"=
text-decoration:none;font-weight:bold;color:#006699;font-size:13px;font-fam=
ily:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;color:#3b5998;t=
ext-decoration:none;font-weight:bold" target=3D"_blank">codinghorror</a><br=
>
<span style=3D"text-align:right;color:#999999;padding-right:5px;fon=
t-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:=
11px">November 28</span>
</td>
</tr>
<tr>
<td style=3D"padding-top:5px" colspan=3D"2">
<p style=3D"margin-top:0;border:0">We&#39;re testing the latest GitHub emai=
l processing library which we are integrating now.</p>
<p style=3D"margin-top:0;border:0"><a href=3D"https://github.com/github/ema=
il_reply_parser" style=3D"text-decoration:none;font-weight:bold;color:#0066=
99" target=3D"_blank">https://github.com/github/email_reply_parser</a></p>
<p style=3D"margin-top:0;border:0">Go ahead and reply to this topic and I&#=
39;ll reply from various email clients for testing.</p>
</td>
</tr>
</tbody>
</table>
<hr style=3D"background-color:#ddd;min-height:1px;border:1px;background-col=
or:#ddd;min-height:1px;border:1px">
<div style=3D"color:#666">
<p>To respond, reply to this email or visit <a href=3D"https://meta.discour=
se.org/t/testing-default-email-replies/22638/3" style=3D"text-decoration:no=
ne;font-weight:bold;color:#006699;color:#666" target=3D"_blank">https://met=
a.discourse.org/t/testing-default-email-replies/22638/3</a> in your browser=
.</p>
</div>
<div style=3D"color:#666">
<p>To unsubscribe from these emails, visit your <a href=3D"https://meta.dis=
course.org/my/preferences" style=3D"text-decoration:none;font-weight:bold;c=
olor:#006699;color:#666" target=3D"_blank">user preferences</a>.</p>
</div>
</div>
</blockquote></div>
--089e0149cfa485c6630508f173df--
This diff is collapsed.
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@discourse.example.com>; Thu, 13 Jun 2013 17:03:50 -0400
Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@discourse.example.com>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
To: reply+636ca428858779856c226bb145ef4fad@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'
Mime-Version: 1.0
Content-Type: text/plain;
charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
Auto-Submitted: auto-generated
X-Sieve: CMU Sieve 2.2
X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
13 Jun 2013 14:03:48 -0700 (PDT)
X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
Test reply to Discourse email digest
Delivered-To: discourse-reply+cd480e301683c9902891f15968bf07a5@discourse.org
Received: by 10.194.216.104 with SMTP id op8csp80593wjc;
Wed, 24 Jul 2013 07:59:14 -0700 (PDT)
Return-Path: <walter.white@googlemail.com>
References: <topic/5043@discourse.org> <51efeb9b36c34_66dc2dfce6811866@discourse.mail>
From: Walter White <walter.white@googlemail.com>
In-Reply-To: <51efeb9b36c34_66dc2dfce6811866@discourse.mail>
Mime-Version: 1.0 (1.0)
Date: Wed, 24 Jul 2013 15:59:10 +0100
Message-ID: <4597127794206131679@unknownmsgid>
Subject: Re: [Discourse] new reply to your post in 'Crystal Blue'
To: walter via Discourse <reply+cd480e301683c9902891f15968bf07a5@appmail.adventuretime.ooo>
Content-Type: multipart/alternative; boundary=001a11c20edc15a39304e2432790
Dit is een antwoord in het Nederlands.
Op 18 juli 2013 10:23 schreef Sander Datema het volgende:
Dit is de originele post.
Delivered-To: reply@discourse.org
Return-Path: <walter.white@googlemail.com>
MIME-Version: 1.0
In-Reply-To: <topic/22638/86406@meta.discourse.org>
References: <topic/22638@meta.discourse.org>
<topic/22638/86406@meta.discourse.org>
Date: Fri, 28 Nov 2014 12:36:49 -0800
Subject: Re: [Discourse Meta] [Lounge] Testing default email replies
From: Walter White <walter.white@googlemail.com>
To: Discourse Meta <reply@discourse.org>
Content-Type: multipart/alternative; boundary=001a11c2e04e6544f30508f138ba
--001a11c2e04e6544f30508f138ba
Content-Type: text/plain; charset=UTF-8
### This is a reply from standard GMail in Google Chrome.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown
fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog.
Here's some **bold** text in Markdown.
Here's a link http://example.com
On Fri, Nov 28, 2014 at 12:35 PM, Arpit Jalan <info@discourse.org> wrote:
> techAPJ <https://meta.discourse.org/users/techapj>
> November 28
>
> Test reply.
>
> First paragraph.
>
> Second paragraph.
>
> To respond, reply to this email or visit
> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in
> your browser.
> ------------------------------
> Previous Replies codinghorror
> <https://meta.discourse.org/users/codinghorror>
> November 28
>
> We're testing the latest GitHub email processing library which we are
> integrating now.
>
> https://github.com/github/email_reply_parser
>
> Go ahead and reply to this topic and I'll reply from various email clients
> for testing.
> ------------------------------
>
> To respond, reply to this email or visit
> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in
> your browser.
>
> To unsubscribe from these emails, visit your user preferences
> <https://meta.discourse.org/my/preferences>.
>
--001a11c2e04e6544f30508f138ba
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
<div dir=3D"ltr"><div>### This is a reply from standard GMail in Google Chr=
ome.</div><div><br></div><div>The quick brown fox jumps over the lazy dog. =
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over=
the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown=
fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. =
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over=
the lazy dog.=C2=A0</div><div><br></div><div>Here&#39;s some **bold** text=
in Markdown.</div><div><br></div><div>Here&#39;s a link <a href=3D"http://=
example.com">http://example.com</a></div></div><div class=3D"gmail_extra"><=
br><div class=3D"gmail_quote">On Fri, Nov 28, 2014 at 12:35 PM, Arpit Jalan=
<span dir=3D"ltr">&lt;<a href=3D"mailto:info@discourse.org" target=3D"_bla=
nk">info@discourse.org</a>&gt;</span> wrote:<br><blockquote class=3D"gmail_=
quote" style=3D"margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1=
ex"><div>
<table style=3D"margin-bottom:25px" cellspacing=3D"0" cellpadding=3D"0" bor=
der=3D"0">
<tbody>
<tr>
<td style=3D"vertical-align:top;width:55px">
<img src=3D"https://meta-discourse.global.ssl.fastly.net/user_avata=
r/meta.discourse.org/techapj/45/3281.png" title=3D"techAPJ" style=3D"max-wi=
dth:100%" width=3D"45" height=3D"45">
</td>
<td>
<a href=3D"https://meta.discourse.org/users/techapj" style=3D"text-=
decoration:none;font-weight:bold;color:#006699;font-size:13px;font-family:&=
#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;color:#3b5998;text-d=
ecoration:none;font-weight:bold" target=3D"_blank">techAPJ</a><br>
<span style=3D"text-align:right;color:#999999;padding-right:5px;fon=
t-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:=
11px">November 28</span>
</td>
</tr>
<tr>
<td style=3D"padding-top:5px" colspan=3D"2">
<p style=3D"margin-top:0;border:0">Test reply.</p>
<p style=3D"margin-top:0;border:0">First paragraph.</p>
<p style=3D"margin-top:0;border:0">Second paragraph.</p>
</td>
</tr>
</tbody>
</table>
<div style=3D"color:#666">
<p>To respond, reply to this email or visit <a href=3D"https://meta.dis=
course.org/t/testing-default-email-replies/22638/3" style=3D"text-decoratio=
n:none;font-weight:bold;color:#006699;color:#666" target=3D"_blank">https:/=
/meta.discourse.org/t/testing-default-email-replies/22638/3</a> in your bro=
wser.</p>
</div>
<hr style=3D"background-color:#ddd;min-height:1px;border:1px;background-c=
olor:#ddd;min-height:1px;border:1px">
<h4>Previous Replies</h4>
<table style=3D"margin-bottom:25px" cellspacing=3D"0" cellpadding=3D"0" b=
order=3D"0">
<tbody>
<tr>
<td style=3D"vertical-align:top;width:55px">
<img src=3D"https://meta-discourse.global.ssl.fastly.net/user_avata=
r/meta.discourse.org/codinghorror/45/5297.png" title=3D"codinghorror" style=
=3D"max-width:100%" width=3D"45" height=3D"45">
</td>
<td>
<a href=3D"https://meta.discourse.org/users/codinghorror" style=3D"=
text-decoration:none;font-weight:bold;color:#006699;font-size:13px;font-fam=
ily:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;color:#3b5998;t=
ext-decoration:none;font-weight:bold" target=3D"_blank">codinghorror</a><br=
>
<span style=3D"text-align:right;color:#999999;padding-right:5px;fon=
t-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:=
11px">November 28</span>
</td>
</tr>
<tr>
<td style=3D"padding-top:5px" colspan=3D"2">
<p style=3D"margin-top:0;border:0">We&#39;re testing the latest GitHub emai=
l processing library which we are integrating now.</p>
<p style=3D"margin-top:0;border:0"><a href=3D"https://github.com/github/ema=
il_reply_parser" style=3D"text-decoration:none;font-weight:bold;color:#0066=
99" target=3D"_blank">https://github.com/github/email_reply_parser</a></p>
<p style=3D"margin-top:0;border:0">Go ahead and reply to this topic and I&#=
39;ll reply from various email clients for testing.</p>
</td>
</tr>
</tbody>
</table>
<hr style=3D"background-color:#ddd;min-height:1px;border:1px;background-col=
or:#ddd;min-height:1px;border:1px">
<div style=3D"color:#666">
<p>To respond, reply to this email or visit <a href=3D"https://meta.discour=
se.org/t/testing-default-email-replies/22638/3" style=3D"text-decoration:no=
ne;font-weight:bold;color:#006699;color:#666" target=3D"_blank">https://met=
a.discourse.org/t/testing-default-email-replies/22638/3</a> in your browser=
.</p>
</div>
<div style=3D"color:#666">
<p>To unsubscribe from these emails, visit your <a href=3D"https://meta.dis=
course.org/my/preferences" style=3D"text-decoration:none;font-weight:bold;c=
olor:#006699;color:#666" target=3D"_blank">user preferences</a>.</p>
</div>
</div>
</blockquote></div><br></div>
--001a11c2e04e6544f30508f138ba--
MIME-Version: 1.0
Received: by 10.25.161.144 with HTTP; Tue, 7 Oct 2014 22:17:17 -0700 (PDT)
X-Originating-IP: [117.207.85.84]
In-Reply-To: <5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail>
References: <topic/35@discourse.techapj.com>
<5434c8b52bb3a_623ff09fec70f049749@discourse-app.mail>
Date: Wed, 8 Oct 2014 10:47:17 +0530
Delivered-To: arpit@techapj.com
Message-ID: <CAOJeqne=SJ_LwN4sb-0Y95ejc2OpreVhdmcPn0TnmwSvTCYzzQ@mail.gmail.com>
Subject: Re: [Discourse] [Meta] Welcome to techAPJ's Discourse!
From: Arpit Jalan <arpit@techapj.com>
To: Discourse <mail+e1c7f2a380e33840aeb654f075490bad@arpitjalan.com>
Content-Type: multipart/alternative; boundary=001a114119d8f4e46e0504e26d5b
--001a114119d8f4e46e0504e26d5b
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
Awesome!
Pleasure to have you here!
:boom:
On Wed, Oct 8, 2014 at 10:46 AM, ajalan <info@unconfigured.discourse.org>
wrote:
> ajalan
> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
JzIjoiVXgxTTZ3eHpuRWF2QXVoZGRJZVN5MWI0WnhrIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
XFxcL3VzZXJzXFxcL2FqYWxhblwiLFwiaWRcIjpcIjgyNWI5MDYzZWNmMDRkMjk5OTE4Nzk1MmU=
5YjY2YjE3XCIsXCJ1cmxfaWRzXCI6W1wiNzA3MTNjNTg4MDI3YWQyM2RiM2QwOTVhOGQwYmY4ZT=
YyMzNjYThiMFwiXX0ifQ>
> October 8
>
> Nice to be here! Thanks! [image: smile]
>
> To respond, reply to this email or visit
> http://discourse.techapj.com/t/welcome-to-techapjs-discourse/35/2
> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
JzIjoid1IyWnVqVGRPU2RwLUlFR0Q5QnI1a203eVNjIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
XFxcL3RcXFwvd2VsY29tZS10by10ZWNoYXBqcy1kaXNjb3Vyc2VcXFwvMzVcXFwvMlwiLFwiaWR=
cIjpcIjgyNWI5MDYzZWNmMDRkMjk5OTE4Nzk1MmU5YjY2YjE3XCIsXCJ1cmxfaWRzXCI6W1wiY2=
RkYzFlZjc5OThhNzE1ODA4Yjg0MGFlNzVlZmNiYmYzYmViODk4Y1wiXX0ifQ>
> in your browser.
> ------------------------------
> Previous Replies techAPJ
> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
JzIjoia2x3LUxac2RSX25uWEFYYWcwVDVha3pIY3RjIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
XFxcL3VzZXJzXFxcL3RlY2hhcGpcIixcImlkXCI6XCI4MjViOTA2M2VjZjA0ZDI5OTkxODc5NTJ=
lOWI2NmIxN1wiLFwidXJsX2lkc1wiOltcIjk2ZjAyMzVhNmM2NzIyNmU1NjhhMzU1NDE1OTAxNz=
AyYTkxNjM1NzJcIl19In0>
> October 8
>
> Welcome to techAPJ's Discourse!
> ------------------------------
>
> To respond, reply to this email or visit
> http://discourse.techapj.com/t/welcome-to-techapjs-discourse/35/2
> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
JzIjoid1IyWnVqVGRPU2RwLUlFR0Q5QnI1a203eVNjIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
XFxcL3RcXFwvd2VsY29tZS10by10ZWNoYXBqcy1kaXNjb3Vyc2VcXFwvMzVcXFwvMlwiLFwiaWR=
cIjpcIjgyNWI5MDYzZWNmMDRkMjk5OTE4Nzk1MmU5YjY2YjE3XCIsXCJ1cmxfaWRzXCI6W1wiY2=
RkYzFlZjc5OThhNzE1ODA4Yjg0MGFlNzVlZmNiYmYzYmViODk4Y1wiXX0ifQ>
> in your browser.
>
> To unsubscribe from these emails, visit your user preferences
> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
JzIjoiVTNudkpobl9lUUl0cmdsVVRrcm5iaHpyN0JZIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
XFxcL215XFxcL3ByZWZlcmVuY2VzXCIsXCJpZFwiOlwiODI1YjkwNjNlY2YwNGQyOTk5MTg3OTU=
yZTliNjZiMTdcIixcInVybF9pZHNcIjpbXCI0OTIyMmMyZDgyNzUwMmQyMGZjYzU4MTZkNjhmYT=
k3NzFkY2YzZDllXCJdfSJ9>
> .
>
--001a114119d8f4e46e0504e26d5b
Content-Type: text/html; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
<div dir=3D"ltr">Awesome!<div><br></div><div>Pleasure to have you here!</di=
v><div><br></div><div>:boom:</div></div><div class=3D"gmail_extra"><br><div=
class=3D"gmail_quote">On Wed, Oct 8, 2014 at 10:46 AM, ajalan <span dir=3D=
"ltr">&lt;<a href=3D"mailto:info@unconfigured.discourse.org" target=3D"_bla=
nk">info@unconfigured.discourse.org</a>&gt;</span> wrote:<br><blockquote cl=
ass=3D"gmail_quote" style=3D"margin:0 0 0 .8ex;border-left:1px #ccc solid;p=
adding-left:1ex"><div>
<table style=3D"margin-bottom:25px;max-width:761px" cellspacing=3D"0" cellp=
adding=3D"0" border=3D"0">
<tbody>
<tr>
<td style=3D"vertical-align:top;width:55px">
<img src=3D"http://discourse.techapj.com/user_avatar/discourse.tech=
apj.com/ajalan/45/35.png" title=3D"ajalan" style=3D"max-width:694px" width=
=3D"45" height=3D"45">
</td>
<td>
<a href=3D"http://mandrillapp.com/track/click/30081177/discourse.te=
chapj.com?p=3DeyJzIjoiVXgxTTZ3eHpuRWF2QXVoZGRJZVN5MWI0WnhrIiwidiI6MSwicCI6I=
ntcInVcIjozMDA4MTE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNl=
LnRlY2hhcGouY29tXFxcL3VzZXJzXFxcL2FqYWxhblwiLFwiaWRcIjpcIjgyNWI5MDYzZWNmMDR=
kMjk5OTE4Nzk1MmU5YjY2YjE3XCIsXCJ1cmxfaWRzXCI6W1wiNzA3MTNjNTg4MDI3YWQyM2RiM2=
QwOTVhOGQwYmY4ZTYyMzNjYThiMFwiXX0ifQ" style=3D"font-size:13px;font-family:&=
#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;color:#3b5998;text-d=
ecoration:none;font-weight:bold;text-decoration:none;font-weight:bold;color=
:#006699" target=3D"_blank">ajalan</a><br>
<span style=3D"text-align:right;color:#999999;padding-right:5px;fon=
t-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:=
11px">October 8</span>
</td>
</tr>
<tr>
<td style=3D"padding-top:5px" colspan=3D"2"><p style=3D"margin-top:0;=
border:0">Nice to be here! Thanks! <img src=3D"http://discourse.techapj.com=
/plugins/emoji/images/smile.png" title=3D":smile:" alt=3D"smile" width=3D"2=
0" height=3D"20"></p></td>
</tr>
</tbody>
</table>
<div style=3D"color:#666">
<p>To respond, reply to this email or visit <a href=3D"http://mandrilla=
pp.com/track/click/30081177/discourse.techapj.com?p=3DeyJzIjoid1IyWnVqVGRPU=
2RwLUlFR0Q5QnI1a203eVNjIiwidiI6MSwicCI6IntcInVcIjozMDA4MTE3NyxcInZcIjoxLFwi=
dXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29tXFxcL3RcXFwvd2VsY29=
tZS10by10ZWNoYXBqcy1kaXNjb3Vyc2VcXFwvMzVcXFwvMlwiLFwiaWRcIjpcIjgyNWI5MDYzZW=
NmMDRkMjk5OTE4Nzk1MmU5YjY2YjE3XCIsXCJ1cmxfaWRzXCI6W1wiY2RkYzFlZjc5OThhNzE1O=
DA4Yjg0MGFlNzVlZmNiYmYzYmViODk4Y1wiXX0ifQ" style=3D"color:#666;text-decorat=
ion:none;font-weight:bold;color:#006699" target=3D"_blank">http://discourse=
.techapj.com/t/welcome-to-techapjs-discourse/35/2</a> in your browser.</p>
</div>
<hr style=3D"background-color:#ddd;min-height:1px;border:1px;background-c=
olor:#ddd;min-height:1px;border:1px">
<h4>Previous Replies</h4>
<table style=3D"margin-bottom:25px;max-width:761px" cellspacing=3D"0" cel=
lpadding=3D"0" border=3D"0">
<tbody>
<tr>
<td style=3D"vertical-align:top;width:55px">
<img src=3D"http://discourse.techapj.com/user_avatar/discourse.tech=
apj.com/techapj/45/34.png" title=3D"techAPJ" style=3D"max-width:694px" widt=
h=3D"45" height=3D"45">
</td>
<td>
<a href=3D"http://mandrillapp.com/track/click/30081177/discourse.te=
chapj.com?p=3DeyJzIjoia2x3LUxac2RSX25uWEFYYWcwVDVha3pIY3RjIiwidiI6MSwicCI6I=
ntcInVcIjozMDA4MTE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNl=
LnRlY2hhcGouY29tXFxcL3VzZXJzXFxcL3RlY2hhcGpcIixcImlkXCI6XCI4MjViOTA2M2VjZjA=
0ZDI5OTkxODc5NTJlOWI2NmIxN1wiLFwidXJsX2lkc1wiOltcIjk2ZjAyMzVhNmM2NzIyNmU1Nj=
hhMzU1NDE1OTAxNzAyYTkxNjM1NzJcIl19In0" style=3D"font-size:13px;font-family:=
&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;color:#3b5998;text-=
decoration:none;font-weight:bold;text-decoration:none;font-weight:bold;colo=
r:#006699" target=3D"_blank">techAPJ</a><br>
<span style=3D"text-align:right;color:#999999;padding-right:5px;fon=
t-family:&#39;lucida grande&#39;,tahoma,verdana,arial,sans-serif;font-size:=
11px">October 8</span>
</td>
</tr>
<tr>
<td style=3D"padding-top:5px" colspan=3D"2"><p style=3D"margin-top:0;=
border:0">Welcome to techAPJ&#39;s Discourse!</p></td>
</tr>
</tbody>
</table>
<hr style=3D"background-color:#ddd;min-height:1px;border:1px;background-col=
or:#ddd;min-height:1px;border:1px">
<div style=3D"color:#666">
<p>To respond, reply to this email or visit <a href=3D"http://mandrillapp.c=
om/track/click/30081177/discourse.techapj.com?p=3DeyJzIjoid1IyWnVqVGRPU2RwL=
UlFR0Q5QnI1a203eVNjIiwidiI6MSwicCI6IntcInVcIjozMDA4MTE3NyxcInZcIjoxLFwidXJs=
XCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29tXFxcL3RcXFwvd2VsY29tZS1=
0by10ZWNoYXBqcy1kaXNjb3Vyc2VcXFwvMzVcXFwvMlwiLFwiaWRcIjpcIjgyNWI5MDYzZWNmMD=
RkMjk5OTE4Nzk1MmU5YjY2YjE3XCIsXCJ1cmxfaWRzXCI6W1wiY2RkYzFlZjc5OThhNzE1ODA4Y=
jg0MGFlNzVlZmNiYmYzYmViODk4Y1wiXX0ifQ" style=3D"color:#666;text-decoration:=
none;font-weight:bold;color:#006699" target=3D"_blank">http://discourse.tec=
hapj.com/t/welcome-to-techapjs-discourse/35/2</a> in your browser.</p>
</div><span class=3D"">
<div style=3D"color:#666">
<p>To unsubscribe from these emails, visit your <a href=3D"http://mandrilla=
pp.com/track/click/30081177/discourse.techapj.com?p=3DeyJzIjoiVTNudkpobl9lU=
Ul0cmdsVVRrcm5iaHpyN0JZIiwidiI6MSwicCI6IntcInVcIjozMDA4MTE3NyxcInZcIjoxLFwi=
dXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29tXFxcL215XFxcL3ByZWZ=
lcmVuY2VzXCIsXCJpZFwiOlwiODI1YjkwNjNlY2YwNGQyOTk5MTg3OTUyZTliNjZiMTdcIixcIn=
VybF9pZHNcIjpbXCI0OTIyMmMyZDgyNzUwMmQyMGZjYzU4MTZkNjhmYTk3NzFkY2YzZDllXCJdf=
SJ9" style=3D"color:#666;text-decoration:none;font-weight:bold;color:#00669=
9" target=3D"_blank">user preferences</a>.</p>
</div>
</span></div>
<img src=3D"http://mandrillapp.com/track/open.php?u=3D30081177&amp;id=3D825=
b9063ecf04d2999187952e9b66b17" height=3D"1" width=3D"1"></blockquote></div>=
<br></div>
--001a114119d8f4e46e0504e26d5b--
MIME-Version: 1.0
In-Reply-To: <reply@discourse-app.mail>
References: <topic/36@discourse.techapj.com>
<5434ced4ee0f9_663fb0b5f76070593b@discourse-app.mail>
Date: Mon, 1 Dec 2014 20:48:40 +0530
Delivered-To: someone@googlemail.com
Subject: Re: [Discourse] [Meta] Testing reply via email
From: Walter White <walter.white@googlemail.com>
To: Discourse <reply@mail.com>
Content-Type: multipart/alternative; boundary=20cf30363f8522466905092920a6
--20cf30363f8522466905092920a6
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
On Wed, Oct 8, 2014 at 11:12 AM, techAPJ <info@unconfigured.discourse.org>
wrote:
> techAPJ <https://meta.discourse.org/users/techapj>
> November 28
>
> Test reply.
>
> First paragraph.
>
> Second paragraph.
>
> To respond, reply to this email or visit
> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in
> your browser.
> ------------------------------
> Previous Replies codinghorror
> <https://meta.discourse.org/users/codinghorror>
> November 28
>
> We're testing the latest GitHub email processing library which we are
> integrating now.
>
> https://github.com/github/email_reply_parser
>
> Go ahead and reply to this topic and I'll reply from various email clients
> for testing.
> ------------------------------
>
> To respond, reply to this email or visit
> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in
> your browser.
>
> To unsubscribe from these emails, visit your user preferences
> <https://meta.discourse.org/my/preferences>.
>
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown
fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog.
--20cf30363f8522466905092920a6--
Delivered-To: reply@discourse.org
Return-Path: <walter.white@googlemail.com>
From: Walter White <walter.white@googlemail.com>
Content-Type: multipart/alternative;
boundary=Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105
Content-Transfer-Encoding: 7bit
Mime-Version: 1.0 (1.0)
Subject: Re: [Discourse Meta] [Lounge] Testing default email replies
Date: Fri, 28 Nov 2014 12:41:41 -0800
References: <topic/22638@meta.discourse.org> <topic/22638/86406@meta.discourse.org>
In-Reply-To: <topic/22638/86406@meta.discourse.org>
To: Discourse Meta <reply@discourse.org>
X-Mailer: iPhone Mail (12B436)
--Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105
Content-Type: text/plain;
charset=us-ascii
Content-Transfer-Encoding: quoted-printable
### this is a reply from iOS default mail
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over t=
he lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fo=
x jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The q=
uick brown fox jumps over the lazy dog. The quick brown fox jumps over the l=
azy dog.=20
Here's some **bold** markdown text.
Here's a link http://example.com
> On Nov 28, 2014, at 12:35 PM, Arpit Jalan <info@discourse.org> wrote:
>=20
>=20
> techAPJ
> November 28
> Test reply.
>=20
> First paragraph.
>=20
> Second paragraph.
>=20
> To respond, reply to this email or visit https://meta.discourse.org/t/test=
ing-default-email-replies/22638/3 in your browser.
>=20
> Previous Replies
>=20
> codinghorror
> November 28
> We're testing the latest GitHub email processing library which we are inte=
grating now.
>=20
> https://github.com/github/email_reply_parser
>=20
> Go ahead and reply to this topic and I'll reply from various email clients=
for testing.
>=20
> To respond, reply to this email or visit https://meta.discourse.org/t/test=
ing-default-email-replies/22638/3 in your browser.
>=20
> To unsubscribe from these emails, visit your user preferences.
--Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105
Content-Type: text/html;
charset=utf-8
Content-Transfer-Encoding: 7bit
<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head><body dir="auto"><div>### this is a reply from iOS default mail</div><div><br></div><div>The quick brown fox jumps over the lazy dog.&nbsp;<span style="background-color: rgba(255, 255, 255, 0);">The quick brown fox jumps over the lazy dog.&nbsp;The quick brown fox jumps over the lazy dog.&nbsp;The quick brown fox jumps over the lazy dog.&nbsp;The quick brown fox jumps over the lazy dog.&nbsp;The quick brown fox jumps over the lazy dog.&nbsp;The quick brown fox jumps over the lazy dog.&nbsp;</span></div><div><br></div><div>Here's some **bold** markdown text.</div><div><br></div><div>Here's a link <a href="http://example.com">http://example.com</a><br><br></div><div><br>On Nov 28, 2014, at 12:35 PM, Arpit Jalan &lt;<a href="mailto:info@discourse.org">info@discourse.org</a>&gt; wrote:<br><br></div><blockquote type="cite"><div><div>
<table style="margin-bottom:25px;" cellspacing="0" cellpadding="0" border="0">
<tbody>
<tr>
<td style="vertical-align:top;width:55px;">
<img src="https://meta-discourse.global.ssl.fastly.net/user_avatar/meta.discourse.org/techapj/45/3281.png" title="techAPJ" style="max-width:100%;" width="45" height="45">
</td>
<td>
<a href="https://meta.discourse.org/users/techapj" target="_blank" style="text-decoration: none; font-weight: bold; color: #006699;; font-size:13px;font-family:'lucida grande',tahoma,verdana,arial,sans-serif;color:#3b5998;text-decoration:none;font-weight:bold">techAPJ</a><br>
<span style="text-align:right;color:#999999;padding-right:5px;font-family:'lucida grande',tahoma,verdana,arial,sans-serif;font-size:11px">November 28</span>
</td>
</tr>
<tr>
<td style="padding-top:5px;" colspan="2">
<p style="margin-top:0; border: 0;">Test reply.</p>
<p style="margin-top:0; border: 0;">First paragraph.</p>
<p style="margin-top:0; border: 0;">Second paragraph.</p>
</td>
</tr>
</tbody>
</table>
<div style="color:#666;">
<p>To respond, reply to this email or visit <a href="https://meta.discourse.org/t/testing-default-email-replies/22638/3" style="text-decoration: none; font-weight: bold; color: #006699;; color:#666;">https://meta.discourse.org/t/testing-default-email-replies/22638/3</a> in your browser.</p>
</div>
<hr style="background-color: #ddd; height: 1px; border: 1px;; background-color: #ddd; height: 1px; border: 1px;">
<h4>Previous Replies</h4>
<table style="margin-bottom:25px;" cellspacing="0" cellpadding="0" border="0">
<tbody>
<tr>
<td style="vertical-align:top;width:55px;">
<img src="https://meta-discourse.global.ssl.fastly.net/user_avatar/meta.discourse.org/codinghorror/45/5297.png" title="codinghorror" style="max-width:100%;" width="45" height="45">
</td>
<td>
<a href="https://meta.discourse.org/users/codinghorror" target="_blank" style="text-decoration: none; font-weight: bold; color: #006699;; font-size:13px;font-family:'lucida grande',tahoma,verdana,arial,sans-serif;color:#3b5998;text-decoration:none;font-weight:bold">codinghorror</a><br>
<span style="text-align:right;color:#999999;padding-right:5px;font-family:'lucida grande',tahoma,verdana,arial,sans-serif;font-size:11px">November 28</span>
</td>
</tr>
<tr>
<td style="padding-top:5px;" colspan="2">
<p style="margin-top:0; border: 0;">We're testing the latest GitHub email processing library which we are integrating now.</p>
<p style="margin-top:0; border: 0;"><a href="https://github.com/github/email_reply_parser" target="_blank" style="text-decoration: none; font-weight: bold; color: #006699;">https://github.com/github/email_reply_parser</a></p>
<p style="margin-top:0; border: 0;">Go ahead and reply to this topic and I'll reply from various email clients for testing.</p>
</td>
</tr>
</tbody>
</table>
<hr style="background-color: #ddd; height: 1px; border: 1px;; background-color: #ddd; height: 1px; border: 1px;">
<div style="color:#666;">
<p>To respond, reply to this email or visit <a href="https://meta.discourse.org/t/testing-default-email-replies/22638/3" style="text-decoration: none; font-weight: bold; color: #006699;; color:#666;">https://meta.discourse.org/t/testing-default-email-replies/22638/3</a> in your browser.</p>
</div>
<div style="color:#666;">
<p>To unsubscribe from these emails, visit your <a href="https://meta.discourse.org/my/preferences" style="text-decoration: none; font-weight: bold; color: #006699;; color:#666;">user preferences</a>.</p>
</div>
</div>
</div></blockquote></body></html>
--Apple-Mail-B41C7F8E-3639-49B0-A5D5-440E125A7105--
In-Reply-To: <test@discourse-app.mail>
Date: Wed, 8 Oct 2014 10:36:19 +0530
Delivered-To: walter.white@googlemail.com
Subject: Re: [Discourse] Welcome to Discourse
From: Walter White <walter.white@googlemail.com>
To: Discourse <mail@arpitjalan.com>
Content-Type: multipart/alternative; boundary=bcaec554078cc3d0c10504e24661
--bcaec554078cc3d0c10504e24661
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
This is my reply.
It is my best reply.
It will also be my *only* reply.
On Wed, Oct 8, 2014 at 10:33 AM, ajalan <info@unconfigured.discourse.org>
wrote:
> ajalan
> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
JzIjoiMGM3a1pGT250VG5sb242RVNTdFdjS1FUSHdzIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
XFxcL3VzZXJzXFxcL2FqYWxhblwiLFwiaWRcIjpcImQxOWYxYjQ5NTdkODRkMGNhZWY1NDEzZGN=
hODA4YTRhXCIsXCJ1cmxfaWRzXCI6W1wiNzA3MTNjNTg4MDI3YWQyM2RiM2QwOTVhOGQwYmY4ZT=
YyMzNjYThiMFwiXX0ifQ>
> October 8
>
> Awesome! Thank You! [image: +1]
>
> To respond, reply to this email or visit
> http://discourse.techapj.com/t/welcome-to-discourse/8/2
> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
JzIjoibzNWaXFDRDdxSFNCbVRkUmdONlRJVW1ENU8wIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
XFxcL3RcXFwvd2VsY29tZS10by1kaXNjb3Vyc2VcXFwvOFxcXC8yXCIsXCJpZFwiOlwiZDE5ZjF=
iNDk1N2Q4NGQwY2FlZjU0MTNkY2E4MDhhNGFcIixcInVybF9pZHNcIjpbXCIwYmFkNjE2NDJkNm=
M2NzJhNGU0ZjYzMGU2ZDA5M2I3MzU3NzQ4MzYxXCJdfSJ9>
> in your browser.
> ------------------------------
> Previous Replies system
> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
JzIjoicjFZQm8ySTJjUEtNclpvekZ5ZmFqYmdpTVFNIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
XFxcL3VzZXJzXFxcL3N5c3RlbVwiLFwiaWRcIjpcImQxOWYxYjQ5NTdkODRkMGNhZWY1NDEzZGN=
hODA4YTRhXCIsXCJ1cmxfaWRzXCI6W1wiMTcxNWU2OTE1M2UzMjk4YmM2Y2NhMWEyM2E5N2ViMW=
U5N2IwMWYyNFwiXX0ifQ>
> October 8
>
> The first paragraph of this pinned topic will be visible as a welcome
> message to all new visitors on your homepage. It's important!
>
> *Edit this* into a brief description of your community:
>
> - Who is it for?
> - What can they find here?
> - Why should they come here?
> - Where can they read more (links, resources, etc)?
>
> You may want to close this topic via the wrench icon at the upper right,
> so that replies don't pile up on an announcement.
> ------------------------------
>
> To respond, reply to this email or visit
> http://discourse.techapj.com/t/welcome-to-discourse/8/2
> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
JzIjoibzNWaXFDRDdxSFNCbVRkUmdONlRJVW1ENU8wIiwidiI6MSwicCI6IntcInVcIjozMDA4M=
TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
XFxcL3RcXFwvd2VsY29tZS10by1kaXNjb3Vyc2VcXFwvOFxcXC8yXCIsXCJpZFwiOlwiZDE5ZjF=
iNDk1N2Q4NGQwY2FlZjU0MTNkY2E4MDhhNGFcIixcInVybF9pZHNcIjpbXCIwYmFkNjE2NDJkNm=
M2NzJhNGU0ZjYzMGU2ZDA5M2I3MzU3NzQ4MzYxXCJdfSJ9>
> in your browser.
>
> To unsubscribe from these emails, visit your user preferences
> <http://mandrillapp.com/track/click/30081177/discourse.techapj.com?p=3Dey=
JzIjoiaFdWSWtiRGIybjJOeWc0VHRrenAzbnhraU93IiwidiI6MSwicCI6IntcInVcIjozMDA4M=
TE3NyxcInZcIjoxLFwidXJsXCI6XCJodHRwOlxcXC9cXFwvZGlzY291cnNlLnRlY2hhcGouY29t=
XFxcL215XFxcL3ByZWZlcmVuY2VzXCIsXCJpZFwiOlwiZDE5ZjFiNDk1N2Q4NGQwY2FlZjU0MTN=
kY2E4MDhhNGFcIixcInVybF9pZHNcIjpbXCI0OTIyMmMyZDgyNzUwMmQyMGZjYzU4MTZkNjhmYT=
k3NzFkY2YzZDllXCJdfSJ9>
> .
>
--bcaec554078cc3d0c10504e24661
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'
Mime-Version: 1.0
Content-Type: text/plain;
charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
X-Sieve: CMU Sieve 2.2
X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
13 Jun 2013 14:03:48 -0700 (PDT)
X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta
<reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo> wrote:
>
>
>
> eviltrout posted in 'Adventure Time Sux' on Discourse Meta:
>
> ---
> hey guys everyone knows adventure time sucks!
>
> ---
> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3
>
> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences).
>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'
Mime-Version: 1.0
Content-Type: text/plain;
charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
X-Sieve: CMU Sieve 2.2
X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
13 Jun 2013 14:03:48 -0700 (PDT)
X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
Is there any reason the *old* candy can't be be kept in silos while the new candy
is imported into *new* silos?
The thing about candy is it stays delicious for a long time -- we can just keep
it there without worrying about it too much, imo.
Thanks for listening.
On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta
<reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo> wrote:
>
>
>
> eviltrout posted in 'Adventure Time Sux' on Discourse Meta:
>
> ---
> hey guys everyone knows adventure time sucks!
>
> ---
> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3
>
> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences).
>
Delivered-To: reply@discourse.org
Return-Path: <walter.white@googlemail.com>
MIME-Version: 1.0
From: <walter.white@googlemail.com>
To:
=?utf-8?Q?Discourse_Meta?=
<reply@discourse.org>
Subject:
=?utf-8?Q?Re:_[Discourse_Meta]_[Lounge]_Testing_default_email_replies?=
Importance: Normal
Date: Fri, 28 Nov 2014 21:29:10 +0000
In-Reply-To: <topic/22638/86406@meta.discourse.org>
References:
<topic/22638@meta.discourse.org>,<topic/22638/86406@meta.discourse.org>
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: base64
IyMjIHJlcGx5IGZyb20gZGVmYXVsdCBtYWlsIGNsaWVudCBpbiBXaW5kb3dzIDguMSBNZXRybw0K
DQoNClRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWlj
ayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gg
anVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0
aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cu
IFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBi
cm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVt
cHMgb3ZlciB0aGUgbGF6eSBkb2cuIFRoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUg
bGF6eSBkb2cuDQoNCg0KVGhpcyBpcyBhICoqYm9sZCoqIHdvcmQgaW4gTWFya2Rvd24NCg0KDQpU
aGlzIGlzIGEgbGluayBodHRwOi8vZXhhbXBsZS5jb20NCiANCg0KDQoNCg0KDQpGcm9tOiBBcnBp
dCBKYWxhbg0KU2VudDog4oCORnJpZGF54oCOLCDigI5Ob3ZlbWJlcuKAjiDigI4yOOKAjiwg4oCO
MjAxNCDigI4xMuKAjjrigI4zNeKAjiDigI5QTQ0KVG86IGplZmYgYXR3b29kDQoNCg0KDQoNCg0K
DQogdGVjaEFQSg0KTm92ZW1iZXIgMjggDQoNClRlc3QgcmVwbHkuDQoNCkZpcnN0IHBhcmFncmFw
aC4NCg0KU2Vjb25kIHBhcmFncmFwaC4NCg0KDQoNClRvIHJlc3BvbmQsIHJlcGx5IHRvIHRoaXMg
ZW1haWwgb3IgdmlzaXQgaHR0cHM6Ly9tZXRhLmRpc2NvdXJzZS5vcmcvdC90ZXN0aW5nLWRlZmF1
bHQtZW1haWwtcmVwbGllcy8yMjYzOC8zIGluIHlvdXIgYnJvd3Nlci4NCg0KDQoNClByZXZpb3Vz
IFJlcGxpZXMNCg0KIGNvZGluZ2hvcnJvcg0KTm92ZW1iZXIgMjggDQoNCldlJ3JlIHRlc3Rpbmcg
dGhlIGxhdGVzdCBHaXRIdWIgZW1haWwgcHJvY2Vzc2luZyBsaWJyYXJ5IHdoaWNoIHdlIGFyZSBp
bnRlZ3JhdGluZyBub3cuDQoNCmh0dHBzOi8vZ2l0aHViLmNvbS9naXRodWIvZW1haWxfcmVwbHlf
cGFyc2VyDQoNCkdvIGFoZWFkIGFuZCByZXBseSB0byB0aGlzIHRvcGljIGFuZCBJJ2xsIHJlcGx5
IGZyb20gdmFyaW91cyBlbWFpbCBjbGllbnRzIGZvciB0ZXN0aW5nLg0KDQoNCg0KDQoNClRvIHJl
c3BvbmQsIHJlcGx5IHRvIHRoaXMgZW1haWwgb3IgdmlzaXQgaHR0cHM6Ly9tZXRhLmRpc2NvdXJz
ZS5vcmcvdC90ZXN0aW5nLWRlZmF1bHQtZW1haWwtcmVwbGllcy8yMjYzOC8zIGluIHlvdXIgYnJv
d3Nlci4NCg0KDQpUbyB1bnN1YnNjcmliZSBmcm9tIHRoZXNlIGVtYWlscywgdmlzaXQgeW91ciB1
c2VyIHByZWZlcmVuY2VzLg==
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'
Mime-Version: 1.0
Content-Type: text/plain;
charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
X-Sieve: CMU Sieve 2.2
X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
13 Jun 2013 14:03:48 -0700 (PDT)
X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
I could not disagree more. I am obviously biased but adventure time is the
greatest show ever created. Everyone should watch it.
- Jake out
On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta
<reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo> wrote:
>
>
>
> eviltrout posted in 'Adventure Time Sux' on Discourse Meta:
>
> ---
> hey guys everyone knows adventure time sucks!
>
> ---
> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3
>
> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences).
>
\ No newline at end of file
This diff is collapsed.
Return-Path: <jake@adventuretime.ooo>
Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@discourse.example.com>; Thu, 13 Jun 2013 17:03:50 -0400
Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@discourse.example.com>; Thu, 13 Jun 2013 14:03:48 -0700
Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
Date: Thu, 13 Jun 2013 17:03:48 -0400
From: Jake the Dog <jake@adventuretime.ooo>
To: reply+QQd8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo
Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'
Mime-Version: 1.0
Content-Type: text/plain;
charset=ISO-8859-1
Content-Transfer-Encoding: 7bit
X-Sieve: CMU Sieve 2.2
X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
13 Jun 2013 14:03:48 -0700 (PDT)
X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
I could not disagree more. I am obviously biased but adventure time is the
greatest show ever created. Everyone should watch it.
- Jake out
On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta
<reply+59d8df8370b7e95c5a49fbf86aeb2c93@discourse.example.com> wrote:
>
>
>
> eviltrout posted in 'Adventure Time Sux' on Discourse Meta:
>
> ---
> hey guys everyone knows adventure time sucks!
>
> ---
> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3
>
> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences).
>
\ No newline at end of file
require "spec_helper"
describe Gitlab::Email::AttachmentUploader do
describe "#execute" do
let(:project) { build(:project) }
let(:message_raw) { fixture_file("emails/attachment.eml") }
let(:message) { Mail::Message.new(message_raw) }
it "uploads all attachments and returns their links" do
links = described_class.new(message).execute(project)
link = links.first
expect(link).not_to be_nil
expect(link[:is_image]).to be_truthy
expect(link[:alt]).to eq("bricks")
expect(link[:url]).to include("/#{project.path_with_namespace}")
expect(link[:url]).to include("bricks.png")
end
end
end
require "spec_helper"
describe Gitlab::Email::Receiver do
before do
stub_reply_by_email_setting(enabled: true, address: "reply+%{reply_key}@appmail.adventuretime.ooo")
end
let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" }
let(:email_raw) { fixture_file('emails/valid_reply.eml') }
let(:project) { create(:project, :public) }
let(:noteable) { create(:issue, project: project) }
let(:user) { create(:user) }
let!(:sent_notification) { SentNotification.record(noteable, user.id, reply_key) }
let(:receiver) { described_class.new(email_raw) }
context "when the recipient address doesn't include a reply key" do
let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(reply_key, "") }
it "raises a SentNotificationNotFoundError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError)
end
end
context "when no sent notificiation for the reply key could be found" do
let(:email_raw) { fixture_file('emails/wrong_reply_key.eml') }
it "raises a SentNotificationNotFoundError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError)
end
end
context "when the email is blank" do
let(:email_raw) { "" }
it "raises an EmptyEmailError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError)
end
end
context "when the email was auto generated" do
let!(:reply_key) { '636ca428858779856c226bb145ef4fad' }
let!(:email_raw) { fixture_file("emails/auto_reply.eml") }
it "raises an AutoGeneratedEmailError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::AutoGeneratedEmailError)
end
end
context "when the user could not be found" do
before do
user.destroy
end
it "raises a UserNotFoundError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotFoundError)
end
end
context "when the user has been blocked" do
before do
user.block
end
it "raises a UserBlockedError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserBlockedError)
end
end
context "when the user is not authorized to create a note" do
before do
project.update_attribute(:visibility_level, Project::PRIVATE)
end
it "raises a UserNotAuthorizedError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotAuthorizedError)
end
end
context "when the noteable could not be found" do
before do
noteable.destroy
end
it "raises a NoteableNotFoundError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::NoteableNotFoundError)
end
end
context "when the reply is blank" do
let!(:email_raw) { fixture_file("emails/no_content_reply.eml") }
it "raises an EmptyEmailError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError)
end
end
context "when the note could not be saved" do
before do
allow_any_instance_of(Note).to receive(:persisted?).and_return(false)
end
it "raises an InvalidNoteError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::InvalidNoteError)
end
end
context "when everything is fine" do
before do
allow_any_instance_of(Gitlab::Email::AttachmentUploader).to receive(:execute).and_return(
[
{
url: "uploads/image.png",
is_image: true,
alt: "image"
}
]
)
end
it "creates a comment" do
expect { receiver.execute }.to change { noteable.notes.count }.by(1)
note = noteable.notes.last
expect(note.author).to eq(sent_notification.recipient)
expect(note.note).to include("I could not disagree more.")
end
it "adds all attachments" do
receiver.execute
note = noteable.notes.last
expect(note.note).to include("![image](uploads/image.png)")
end
end
end
require "spec_helper"
# Inspired in great part by Discourse's Email::Receiver
describe Gitlab::Email::ReplyParser do
describe '#execute' do
def test_parse_body(mail_string)
described_class.new(Mail::Message.new(mail_string)).execute
end
it "returns an empty string if the message is blank" do
expect(test_parse_body("")).to eq("")
end
it "returns an empty string if the message is not an email" do
expect(test_parse_body("asdf" * 30)).to eq("")
end
it "returns an empty string if there is no reply content" do
expect(test_parse_body(fixture_file("emails/no_content_reply.eml"))).to eq("")
end
it "properly renders plaintext-only email" do
expect(test_parse_body(fixture_file("emails/plaintext_only.eml"))).
to eq(
<<-BODY.strip_heredoc.chomp
### reply from default mail client in Windows 8.1 Metro
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
This is a **bold** word in Markdown
This is a link http://example.com
BODY
)
end
it "supports a Dutch reply" do
expect(test_parse_body(fixture_file("emails/dutch.eml"))).to eq("Dit is een antwoord in het Nederlands.")
end
it "removes an 'on date wrote' quoting line" do
expect(test_parse_body(fixture_file("emails/on_wrote.eml"))).to eq("Sure, all you need to do is frobnicate the foobar and you'll be all set!")
end
it "handles multiple paragraphs" do
expect(test_parse_body(fixture_file("emails/paragraphs.eml"))).
to eq(
<<-BODY.strip_heredoc.chomp
Is there any reason the *old* candy can't be be kept in silos while the new candy
is imported into *new* silos?
The thing about candy is it stays delicious for a long time -- we can just keep
it there without worrying about it too much, imo.
Thanks for listening.
BODY
)
end
it "handles multiple paragraphs when parsing html" do
expect(test_parse_body(fixture_file("emails/html_paragraphs.eml"))).
to eq(
<<-BODY.strip_heredoc.chomp
Awesome!
Pleasure to have you here!
:boom:
BODY
)
end
it "handles newlines" do
expect(test_parse_body(fixture_file("emails/newlines.eml"))).
to eq(
<<-BODY.strip_heredoc.chomp
This is my reply.
It is my best reply.
It will also be my *only* reply.
BODY
)
end
it "handles inline reply" do
expect(test_parse_body(fixture_file("emails/inline_reply.eml"))).
to eq(
<<-BODY.strip_heredoc.chomp
On Wed, Oct 8, 2014 at 11:12 AM, techAPJ <info@unconfigured.discourse.org> wrote:
> techAPJ <https://meta.discourse.org/users/techapj>
> November 28
>
> Test reply.
>
> First paragraph.
>
> Second paragraph.
>
> To respond, reply to this email or visit
> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in
> your browser.
> ------------------------------
> Previous Replies codinghorror
> <https://meta.discourse.org/users/codinghorror>
> November 28
>
> We're testing the latest GitHub email processing library which we are
> integrating now.
>
> https://github.com/github/email_reply_parser
>
> Go ahead and reply to this topic and I'll reply from various email clients
> for testing.
> ------------------------------
>
> To respond, reply to this email or visit
> https://meta.discourse.org/t/testing-default-email-replies/22638/3 in
> your browser.
>
> To unsubscribe from these emails, visit your user preferences
> <https://meta.discourse.org/my/preferences>.
>
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown
fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog.
BODY
)
end
it "properly renders email reply from gmail web client" do
expect(test_parse_body(fixture_file("emails/gmail_web.eml"))).
to eq(
<<-BODY.strip_heredoc.chomp
### This is a reply from standard GMail in Google Chrome.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown
fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog.
Here's some **bold** text in Markdown.
Here's a link http://example.com
BODY
)
end
it "properly renders email reply from iOS default mail client" do
expect(test_parse_body(fixture_file("emails/ios_default.eml"))).
to eq(
<<-BODY.strip_heredoc.chomp
### this is a reply from iOS default mail
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
Here's some **bold** markdown text.
Here's a link http://example.com
BODY
)
end
it "properly renders email reply from Android 5 gmail client" do
expect(test_parse_body(fixture_file("emails/android_gmail.eml"))).
to eq(
<<-BODY.strip_heredoc.chomp
### this is a reply from Android 5 gmail
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over
the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown
fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
The quick brown fox jumps over the lazy dog.
This is **bold** in Markdown.
This is a link to http://example.com
BODY
)
end
it "properly renders email reply from Windows 8.1 Metro default mail client" do
expect(test_parse_body(fixture_file("emails/windows_8_metro.eml"))).
to eq(
<<-BODY.strip_heredoc.chomp
### reply from default mail client in Windows 8.1 Metro
The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
This is a **bold** word in Markdown
This is a link http://example.com
BODY
)
end
it "properly renders email reply from MS Outlook client" do
expect(test_parse_body(fixture_file("emails/outlook.eml"))).to eq("Microsoft Outlook 2010")
end
end
end
require "spec_helper" require "spec_helper"
describe Gitlab::GoogleCodeImport::Client do describe Gitlab::GoogleCodeImport::Client do
let(:raw_data) { JSON.parse(File.read(Rails.root.join("spec/fixtures/GoogleCodeProjectHosting.json"))) } let(:raw_data) { JSON.parse(fixture_file("GoogleCodeProjectHosting.json")) }
subject { described_class.new(raw_data) } subject { described_class.new(raw_data) }
describe "#valid?" do describe "#valid?" do
......
...@@ -2,7 +2,7 @@ require "spec_helper" ...@@ -2,7 +2,7 @@ require "spec_helper"
describe Gitlab::GoogleCodeImport::Importer do describe Gitlab::GoogleCodeImport::Importer do
let(:mapped_user) { create(:user, username: "thilo123") } let(:mapped_user) { create(:user, username: "thilo123") }
let(:raw_data) { JSON.parse(File.read(Rails.root.join("spec/fixtures/GoogleCodeProjectHosting.json"))) } let(:raw_data) { JSON.parse(fixture_file("GoogleCodeProjectHosting.json")) }
let(:client) { Gitlab::GoogleCodeImport::Client.new(raw_data) } let(:client) { Gitlab::GoogleCodeImport::Client.new(raw_data) }
let(:import_data) do let(:import_data) do
{ {
......
require "spec_helper"
describe Gitlab::ReplyByEmail do
describe "self.enabled?" do
context "when reply by email is enabled" do
before do
stub_reply_by_email_setting(enabled: true)
end
context "when the address is valid" do
before do
stub_reply_by_email_setting(address: "replies+%{reply_key}@example.com")
end
it "returns true" do
expect(described_class.enabled?).to be_truthy
end
end
context "when the address is invalid" do
before do
stub_reply_by_email_setting(address: "replies@example.com")
end
it "returns false" do
expect(described_class.enabled?).to be_falsey
end
end
end
context "when reply by email is disabled" do
before do
stub_reply_by_email_setting(enabled: false)
end
it "returns false" do
expect(described_class.enabled?).to be_falsey
end
end
end
describe "self.reply_key" do
context "when enabled" do
before do
allow(described_class).to receive(:enabled?).and_return(true)
end
it "returns a random hex" do
key = described_class.reply_key
key2 = described_class.reply_key
expect(key).not_to eq(key2)
end
end
context "when disabled" do
before do
allow(described_class).to receive(:enabled?).and_return(false)
end
it "returns nil" do
expect(described_class.reply_key).to be_nil
end
end
end
context "self.reply_address" do
before do
stub_reply_by_email_setting(address: "replies+%{reply_key}@example.com")
end
it "returns the address with an interpolated reply key" do
expect(described_class.reply_address("key")).to eq("replies+key@example.com")
end
end
context "self.reply_key_from_address" do
before do
stub_reply_by_email_setting(address: "replies+%{reply_key}@example.com")
end
it "returns reply key" do
expect(described_class.reply_key_from_address("replies+key@example.com")).to eq("key")
end
end
end
...@@ -13,13 +13,13 @@ describe Projects::UploadService do ...@@ -13,13 +13,13 @@ describe Projects::UploadService do
@link_to_file = upload_file(@project.repository, gif) @link_to_file = upload_file(@project.repository, gif)
end end
it { expect(@link_to_file).to have_key('alt') } it { expect(@link_to_file).to have_key(:alt) }
it { expect(@link_to_file).to have_key('url') } it { expect(@link_to_file).to have_key(:url) }
it { expect(@link_to_file).to have_key('is_image') } it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file).to have_value('banana_sample') } it { expect(@link_to_file).to have_value('banana_sample') }
it { expect(@link_to_file['is_image']).to equal(true) } it { expect(@link_to_file[:is_image]).to equal(true) }
it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file['url']).to match('banana_sample.gif') } it { expect(@link_to_file[:url]).to match('banana_sample.gif') }
end end
context 'for valid png file' do context 'for valid png file' do
...@@ -29,13 +29,13 @@ describe Projects::UploadService do ...@@ -29,13 +29,13 @@ describe Projects::UploadService do
@link_to_file = upload_file(@project.repository, png) @link_to_file = upload_file(@project.repository, png)
end end
it { expect(@link_to_file).to have_key('alt') } it { expect(@link_to_file).to have_key(:alt) }
it { expect(@link_to_file).to have_key('url') } it { expect(@link_to_file).to have_key(:url) }
it { expect(@link_to_file).to have_value('dk') } it { expect(@link_to_file).to have_value('dk') }
it { expect(@link_to_file).to have_key('is_image') } it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file['is_image']).to equal(true) } it { expect(@link_to_file[:is_image]).to equal(true) }
it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file['url']).to match('dk.png') } it { expect(@link_to_file[:url]).to match('dk.png') }
end end
context 'for valid jpg file' do context 'for valid jpg file' do
...@@ -44,13 +44,13 @@ describe Projects::UploadService do ...@@ -44,13 +44,13 @@ describe Projects::UploadService do
@link_to_file = upload_file(@project.repository, jpg) @link_to_file = upload_file(@project.repository, jpg)
end end
it { expect(@link_to_file).to have_key('alt') } it { expect(@link_to_file).to have_key(:alt) }
it { expect(@link_to_file).to have_key('url') } it { expect(@link_to_file).to have_key(:url) }
it { expect(@link_to_file).to have_key('is_image') } it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file).to have_value('rails_sample') } it { expect(@link_to_file).to have_value('rails_sample') }
it { expect(@link_to_file['is_image']).to equal(true) } it { expect(@link_to_file[:is_image]).to equal(true) }
it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file['url']).to match('rails_sample.jpg') } it { expect(@link_to_file[:url]).to match('rails_sample.jpg') }
end end
context 'for txt file' do context 'for txt file' do
...@@ -59,13 +59,13 @@ describe Projects::UploadService do ...@@ -59,13 +59,13 @@ describe Projects::UploadService do
@link_to_file = upload_file(@project.repository, txt) @link_to_file = upload_file(@project.repository, txt)
end end
it { expect(@link_to_file).to have_key('alt') } it { expect(@link_to_file).to have_key(:alt) }
it { expect(@link_to_file).to have_key('url') } it { expect(@link_to_file).to have_key(:url) }
it { expect(@link_to_file).to have_key('is_image') } it { expect(@link_to_file).to have_key(:is_image) }
it { expect(@link_to_file).to have_value('doc_sample.txt') } it { expect(@link_to_file).to have_value('doc_sample.txt') }
it { expect(@link_to_file['is_image']).to equal(false) } it { expect(@link_to_file[:is_image]).to equal(false) }
it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } it { expect(@link_to_file[:url]).to match("/#{@project.path_with_namespace}") }
it { expect(@link_to_file['url']).to match('doc_sample.txt') } it { expect(@link_to_file[:url]).to match('doc_sample.txt') }
end end
context 'for too large a file' do context 'for too large a file' do
......
module FixtureHelpers
def fixture_file(filename)
return '' if filename.blank?
file_path = File.expand_path(Rails.root.join('spec/fixtures/', filename))
File.read(file_path)
end
end
RSpec.configure do |config|
config.include FixtureHelpers
end
...@@ -100,7 +100,7 @@ class MarkdownFeature ...@@ -100,7 +100,7 @@ class MarkdownFeature
end end
def raw_markdown def raw_markdown
fixture = Rails.root.join('spec/fixtures/markdown.md.erb') markdown = File.read(Rails.root.join('spec/fixtures/markdown.md.erb'))
ERB.new(File.read(fixture)).result(binding) ERB.new(markdown).result(binding)
end end
end end
...@@ -17,6 +17,10 @@ module StubConfiguration ...@@ -17,6 +17,10 @@ module StubConfiguration
allow(Gitlab.config.gravatar).to receive_messages(messages) allow(Gitlab.config.gravatar).to receive_messages(messages)
end end
def stub_reply_by_email_setting(messages)
allow(Gitlab.config.reply_by_email).to receive_messages(messages)
end
private private
# Modifies stubbed messages to also stub possible predicate versions # Modifies stubbed messages to also stub possible predicate versions
......
require "spec_helper"
describe EmailReceiverWorker do
let(:raw_message) { fixture_file('emails/valid_reply.eml') }
context "when reply by email is enabled" do
before do
allow(Gitlab::ReplyByEmail).to receive(:enabled?).and_return(true)
end
it "calls the email receiver" do
expect(Gitlab::Email::Receiver).to receive(:new).with(raw_message).and_call_original
expect_any_instance_of(Gitlab::Email::Receiver).to receive(:execute)
described_class.new.perform(raw_message)
end
context "when an error occurs" do
before do
allow_any_instance_of(Gitlab::Email::Receiver).to receive(:execute).and_raise(Gitlab::Email::Receiver::EmptyEmailError)
end
it "sends out a rejection email" do
described_class.new.perform(raw_message)
email = ActionMailer::Base.deliveries.last
expect(email).not_to be_nil
expect(email.to).to eq(["jake@adventuretime.ooo"])
expect(email.subject).to include("Rejected")
end
end
end
context "when reply by email is disabled" do
before do
allow(Gitlab::ReplyByEmail).to receive(:enabled?).and_return(false)
end
it "doesn't call the email receiver" do
expect(Gitlab::Email::Receiver).not_to receive(:new)
described_class.new.perform(raw_message)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment