2013-06-10 15:33:37 -04:00
#
# A helper class to send an email. It will also handle a nil message, which it considers
# to be "do nothing". This is because some Mailers will decide not to do work for some
# reason. For example, emailing a user too frequently. A nil to address is also considered
# "do nothing"
#
# It also adds an HTML part for the plain text body
#
require_dependency 'email/renderer'
2013-07-02 14:13:46 -04:00
require 'uri'
2014-03-12 11:55:08 +01:00
require 'net/smtp'
2013-06-10 15:33:37 -04:00
2014-03-07 16:33:15 +01:00
SMTP_CLIENT_ERRORS = [ Net :: SMTPFatalError , Net :: SMTPSyntaxError ]
2013-06-10 15:33:37 -04:00
module Email
class Sender
def initialize ( message , email_type , user = nil )
@message = message
@email_type = email_type
@user = user
end
def send
2016-04-08 10:53:16 +05:30
return if SiteSetting . disable_emails && @email_type . to_s != " admin_login "
2016-02-17 17:31:46 +01:00
return if ActionMailer :: Base :: NullMail === @message
return if ActionMailer :: Base :: NullMail === ( @message . message rescue nil )
2016-02-15 17:53:07 +01:00
return skip ( I18n . t ( 'email_log.message_blank' ) ) if @message . blank?
2014-02-14 13:06:21 -05:00
return skip ( I18n . t ( 'email_log.message_to_blank' ) ) if @message . to . blank?
2013-11-28 17:20:56 -05:00
if @message . text_part
2014-02-14 13:06:21 -05:00
return skip ( I18n . t ( 'email_log.text_part_body_blank' ) ) if @message . text_part . body . to_s . blank?
2013-11-28 17:20:56 -05:00
else
2014-02-14 13:06:21 -05:00
return skip ( I18n . t ( 'email_log.body_blank' ) ) if @message . body . to_s . blank?
2013-11-28 17:20:56 -05:00
end
2013-06-10 15:33:37 -04:00
@message . charset = 'UTF-8'
opts = { }
renderer = Email :: Renderer . new ( @message , opts )
2013-11-29 12:21:21 -05:00
if @message . html_part
@message . html_part . body = renderer . html
else
2013-07-24 17:13:15 +10:00
@message . html_part = Mail :: Part . new do
content_type 'text/html; charset=UTF-8'
body renderer . html
end
2013-06-10 15:33:37 -04:00
end
2013-07-24 15:07:43 -04:00
@message . parts [ 0 ] . body = @message . parts [ 0 ] . body . to_s . gsub ( / \ [ \/ ?email-indent \ ] / , '' )
2013-07-22 15:06:37 -04:00
2015-01-17 02:07:58 -08:00
# Fix relative (ie upload) HTML links in markdown which do not work well in plain text emails.
# These are the links we add when a user uploads a file or image.
# Ideally we would parse general markdown into plain text, but that is almost an intractable problem.
url_prefix = Discourse . base_url
@message . parts [ 0 ] . body = @message . parts [ 0 ] . body . to_s . gsub ( / <a class="attachment" href="( \/ uploads \/ default \/ [^"]+)">([^<]*)< \/ a> / , '[\2](' + url_prefix + '\1)' )
@message . parts [ 0 ] . body = @message . parts [ 0 ] . body . to_s . gsub ( / <img src="( \/ uploads \/ default \/ [^"]+)"([^>]*)> / , '' )
2013-06-10 15:33:37 -04:00
@message . text_part . content_type = 'text/plain; charset=UTF-8'
2013-06-25 11:35:26 -04:00
# Set up the email log
2016-01-29 16:49:49 +01:00
email_log = EmailLog . new ( email_type : @email_type , to_address : to_address , user_id : @user . try ( :id ) )
2013-07-02 14:13:46 -04:00
2013-07-08 11:48:40 -04:00
host = Email :: Sender . host_for ( Discourse . base_url )
2013-07-02 14:13:46 -04:00
2013-07-08 11:48:40 -04:00
topic_id = header_value ( 'X-Discourse-Topic-Id' )
post_id = header_value ( 'X-Discourse-Post-Id' )
reply_key = header_value ( 'X-Discourse-Reply-Key' )
2015-01-28 14:42:49 +05:30
# always set a default Message ID from the host
uuid = SecureRandom . uuid
@message . header [ 'Message-ID' ] = " < #{ uuid } @ #{ host } > "
2013-07-08 11:48:40 -04:00
if topic_id . present?
email_log . topic_id = topic_id
2016-04-06 21:04:30 +02:00
incoming_email = IncomingEmail . find_by ( post_id : post_id , topic_id : topic_id )
incoming_message_id = nil
incoming_message_id = " < #{ incoming_email . message_id } > " if incoming_email . try ( :message_id ) . present?
2014-06-13 15:42:14 -07:00
topic_identifier = " <topic/ #{ topic_id } @ #{ host } > "
2014-11-26 16:35:56 -08:00
post_identifier = " <topic/ #{ topic_id } / #{ post_id } @ #{ host } > "
2016-04-06 21:04:30 +02:00
2014-11-26 16:35:56 -08:00
@message . header [ 'Message-ID' ] = post_identifier
2016-04-06 21:04:30 +02:00
@message . header [ 'In-Reply-To' ] = incoming_message_id || topic_identifier
2014-06-13 15:42:14 -07:00
@message . header [ 'References' ] = topic_identifier
2014-10-08 23:39:21 +05:30
topic = Topic . where ( id : topic_id ) . first
2014-06-13 15:42:14 -07:00
# http://www.ietf.org/rfc/rfc2919.txt
2014-10-08 23:39:21 +05:30
if topic && topic . category && ! topic . category . uncategorized?
2016-06-10 21:37:33 -05:00
list_id = " < #{ topic . category . name . downcase . tr ( ' ' , '-' ) } . #{ host } > "
2014-10-08 23:39:21 +05:30
# subcategory case
if ! topic . category . parent_category_id . nil?
parent_category_name = Category . find_by ( id : topic . category . parent_category_id ) . name
2016-06-10 21:37:33 -05:00
list_id = " < #{ topic . category . name . downcase . tr ( ' ' , '-' ) } . #{ parent_category_name . downcase . tr ( ' ' , '-' ) } . #{ host } > "
2014-10-08 23:39:21 +05:30
end
else
list_id = " < #{ host } > "
end
2014-10-09 01:27:30 +05:30
# http://www.ietf.org/rfc/rfc3834.txt
2016-01-29 16:49:49 +01:00
@message . header [ 'Precedence' ] = 'list'
@message . header [ 'List-ID' ] = list_id
@message . header [ 'List-Archive' ] = topic . url if topic
2014-06-13 15:49:11 -07:00
end
2016-01-29 16:49:49 +01:00
if reply_key . present? && @message . header [ 'Reply-To' ] =~ / \ <([^ \ >]+) \ > /
email = Regexp . last_match [ 1 ]
@message . header [ 'List-Post' ] = " <mailto: #{ email } > "
2013-07-08 11:48:40 -04:00
end
2016-04-25 20:06:45 +02:00
if SiteSetting . reply_by_email_address . present? && SiteSetting . reply_by_email_address [ " + " ]
2016-04-18 17:13:41 +10:00
email_log . bounce_key = SecureRandom . hex
# WARNING: RFC claims you can not set the Return Path header, this is 100% correct
# however Rails has special handling for this header and ends up using this value
# as the Envelope From address so stuff works as expected
2016-04-25 20:06:45 +02:00
@message . header [ :return_path ] = SiteSetting . reply_by_email_address . sub ( " %{reply_key} " , " verp- #{ email_log . bounce_key } " )
2016-04-18 17:13:41 +10:00
end
2013-07-08 11:48:40 -04:00
email_log . post_id = post_id if post_id . present?
email_log . reply_key = reply_key if reply_key . present?
2013-06-13 10:56:16 -04:00
2013-06-25 11:35:26 -04:00
# Remove headers we don't need anymore
2016-01-29 16:49:49 +01:00
@message . header [ 'X-Discourse-Topic-Id' ] = nil if topic_id . present?
@message . header [ 'X-Discourse-Post-Id' ] = nil if post_id . present?
2015-01-29 17:23:10 +05:30
@message . header [ 'X-Discourse-Reply-Key' ] = nil if reply_key . present?
2013-06-25 11:35:26 -04:00
2016-06-13 12:31:01 +02:00
# pass the original message_id when using mailjet/mandrill
case ActionMailer :: Base . smtp_settings [ :address ]
when / \ .mailjet \ .com /
2016-06-06 19:47:45 +02:00
@message . header [ 'X-MJ-CustomID' ] = @message . message_id
2016-06-13 12:31:01 +02:00
when " smtp.mandrillapp.com "
@message . header [ 'X-MC-Metadata' ] = { message_id : @message . message_id } . to_json
2016-06-06 19:47:45 +02:00
end
2014-09-13 10:56:31 +05:30
# Suppress images from short emails
2016-01-29 16:49:49 +01:00
if SiteSetting . strip_images_from_short_emails &&
2016-05-09 20:37:33 +02:00
@message . html_part . body . to_s . bytesize < = SiteSetting . short_email_length &&
@message . html_part . body =~ / <img[^>]+> /
2014-09-13 10:56:31 +05:30
style = Email :: Styles . new ( @message . html_part . body . to_s )
@message . html_part . body = style . strip_avatars_and_emojis
end
2016-06-01 21:48:06 +02:00
email_log . message_id = @message . message_id
2014-03-07 16:33:15 +01:00
begin
2014-10-15 00:04:47 -07:00
@message . deliver_now
2014-03-09 23:06:54 +11:00
rescue * SMTP_CLIENT_ERRORS = > e
2014-03-07 16:33:15 +01:00
return skip ( e . message )
end
2013-06-25 11:35:26 -04:00
# Save and return the email log
2013-06-13 10:56:16 -04:00
email_log . save!
email_log
2013-06-10 15:33:37 -04:00
end
2014-02-14 13:06:21 -05:00
def to_address
@to_address || = begin
2016-02-15 17:53:07 +01:00
to = @message . try ( :to )
2016-01-29 16:49:49 +01:00
to = to . first if Array === to
to . presence || " no_email_found "
2014-02-14 13:06:21 -05:00
end
end
2013-07-08 11:48:40 -04:00
def self . host_for ( base_url )
2013-07-02 14:13:46 -04:00
host = " localhost "
if base_url . present?
begin
uri = URI . parse ( base_url )
host = uri . host . downcase if uri . host . present?
rescue URI :: InvalidURIError
end
end
2013-07-08 11:48:40 -04:00
host
end
2013-07-02 14:13:46 -04:00
2013-06-13 18:11:10 -04:00
private
2013-07-08 11:48:40 -04:00
def header_value ( name )
2013-06-13 18:11:10 -04:00
header = @message . header [ name ]
2013-07-08 11:48:40 -04:00
return nil unless header
header . value
2013-06-13 18:11:10 -04:00
end
2014-02-14 13:06:21 -05:00
def skip ( reason )
2016-01-29 16:49:49 +01:00
EmailLog . create! (
email_type : @email_type ,
to_address : to_address ,
user_id : @user . try ( :id ) ,
skipped : true ,
2016-02-15 17:53:07 +01:00
skipped_reason : " [Sender] #{ reason } "
2016-01-29 16:49:49 +01:00
)
2014-02-14 13:06:21 -05:00
end
2013-06-10 15:33:37 -04:00
end
2013-07-24 17:13:15 +10:00
end