Skip to content

Commit

Permalink
Merge e0d1d19 into 86175d6
Browse files Browse the repository at this point in the history
  • Loading branch information
mlandauer committed Aug 13, 2020
2 parents 86175d6 + e0d1d19 commit a97ecb8
Show file tree
Hide file tree
Showing 17 changed files with 169 additions and 61 deletions.
8 changes: 6 additions & 2 deletions app/graphql/mutations/create_emails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ class CreateEmails < Mutations::Base
argument :text_part, String, required: false
argument :html_part, String, required: false

argument :ignore_deny_list, Boolean, required: false

field :emails, [Types::Email], null: true

# rubocop:disable Naming/MethodParameterName
def resolve(
app_id:, from:, to:, cc: [], subject:, text_part: nil, html_part: nil
app_id:, from:, to:, cc: [], subject:, text_part: nil, html_part: nil,
ignore_deny_list: false
)
create_email = EmailServices::Create.call(
app_id: app_id,
Expand All @@ -32,7 +35,8 @@ def resolve(
cc: cc,
subject: subject,
text_part: text_part,
html_part: html_part
html_part: html_part,
ignore_deny_list: ignore_deny_list
)
# TODO: Error checking
{
Expand Down
7 changes: 7 additions & 0 deletions app/graphql/types/email.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ class Email < GraphQL::Schema::Object
field :clicked, Boolean,
null: false,
description: "Whether this email was clicked"
field :ignore_deny_list, Boolean,
null: false,
description: "If true the delivery of this email ignores whether the destination address is in the deny list"
field :delivery_events, [Types::DeliveryEvent],
null: false,
description: "A list of delivery events for this email"
Expand Down Expand Up @@ -70,6 +73,10 @@ def clicked
object.delivery_links.any?(&:clicked?)
end

def ignore_deny_list
object.ignore_deny_list
end

def delivery_events
object.postfix_log_lines
end
Expand Down
3 changes: 2 additions & 1 deletion app/models/delivery.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Delivery < ActiveRecord::Base

delegate :from, :from_address, :from_domain, :text_part, :html_part, :data,
:click_tracking_enabled?, :open_tracking_enabled?, :subject,
:ignore_deny_list,
to: :email

delegate :tracking_domain_info, to: :app
Expand All @@ -29,7 +30,7 @@ def send?
# If there is no team there is no deny list
# In concrete terms the internal cuttlefish app doesn't have a deny
# list and isn't part of a team
app.team.nil? || address.deny_lists.find_by(team_id: app.team.id).nil?
app.team.nil? || email.ignore_deny_list || address.deny_lists.find_by(team_id: app.team.id).nil?
end

def add_open_event(request)
Expand Down
8 changes: 5 additions & 3 deletions app/services/email_services/create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
module EmailServices
class Create < ApplicationService
# rubocop:disable Naming/MethodParameterName
def initialize(app_id:, from:, to:, cc:, subject:, text_part:, html_part:)
def initialize(app_id:, from:, to:, cc:, subject:, text_part:, html_part:, ignore_deny_list:)
super()
@app_id = app_id
@from = from
Expand All @@ -12,6 +12,7 @@ def initialize(app_id:, from:, to:, cc:, subject:, text_part:, html_part:)
@subject = subject
@text_part = text_part
@html_part = html_part
@ignore_deny_list = ignore_deny_list
end
# rubocop:enable Naming/MethodParameterName

Expand Down Expand Up @@ -40,7 +41,8 @@ def call
email = Email.create!(
to: mail.to,
data: mail.to_s,
app_id: app_id
app_id: app_id,
ignore_deny_list: ignore_deny_list
)

SendEmailWorker.perform_async(email.id)
Expand All @@ -50,6 +52,6 @@ def call

private

attr_reader :app_id, :from, :to, :cc, :subject, :text_part, :html_part
attr_reader :app_id, :from, :to, :cc, :subject, :text_part, :html_part, :ignore_deny_list
end
end
2 changes: 2 additions & 0 deletions app/views/deliveries/show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
%h1= @delivery.subject

%p
- if @delivery.ignore_deny_list
%span.label.label-warning Ignore Deny List
%span{class: label_class(@delivery.status)}
= status_name(@delivery.status)
- if @delivery.opened?
Expand Down
33 changes: 33 additions & 0 deletions app/views/documentation/_special_headers.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
%h3 Special Headers

%p
By setting special headers in the email content you can make some special things happen.
There is currently only one setting.

%h4 X-Cuttlefish-Ignore-Deny-List

%p
If you set this to "true", the email will get sent even if the destination
address is on the "deny list". Note that if the email hard bounces the destination
address will still get added to the deny list.

%p
Only use this in very exceptional circumstances where
you have a low volume of email going to an email address that you're very
confident should work and you absolutely don't want emails to not get sent.

%p
One example of this is sending emails to a known and fixed email address of
a government agency. We <strong>really</strong> want those emails to arrive as much as
possible.

%p An example of the header in use:

:coderay
#!plain
From: foo@bar.com
To: wibble@wobble.com
X-Cuttlefish-Ignore-Deny-List: true
Subject: Hello

Hello!
1 change: 1 addition & 0 deletions app/views/documentation/index.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- @apps.each do |app|
.tab-pane{id: "app-#{app.id}", class: ("active" if app == @active_app)}
= render partial: "general", locals: {app: app}
= render partial: "special_headers", locals: {app: app}
= render partial: "rails", locals: {app: app}
= render partial: "php", locals: {app: app}
= render partial: "django", locals: {app: app}
Expand Down
6 changes: 6 additions & 0 deletions db/migrate/20200812050040_add_ignore_deny_list_to_emails.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class AddIgnoreDenyListToEmails < ActiveRecord::Migration[5.2]
def change
add_column :emails, :ignore_deny_list, :boolean, null: false, default: false
change_column_default :emails, :ignore_deny_list, from: false, to: nil
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2018_10_11_045125) do
ActiveRecord::Schema.define(version: 2020_08_12_050040) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -128,6 +128,7 @@
t.string "data_hash", limit: 255
t.integer "app_id", null: false
t.string "subject", limit: 255
t.boolean "ignore_deny_list", null: false
t.index ["app_id"], name: "index_emails_on_app_id"
t.index ["created_at"], name: "index_emails_on_created_at"
t.index ["from_address_id"], name: "index_emails_on_from_address_id"
Expand Down
4 changes: 4 additions & 0 deletions db/seeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@

email = acting_app.emails.create!(
from_address_id: address1.id,
ignore_deny_list: true,
data: <<~EMAIL
From: foo@bar.com
To: foo@example.com
Expand Down Expand Up @@ -128,6 +129,7 @@

email = office_app.emails.create!(
from_address_id: address1.id,
ignore_deny_list: false,
data: <<~EMAIL
From: foo@bar.com
To: foo@example.com
Expand Down Expand Up @@ -168,6 +170,7 @@

email = acting_app.emails.create!(
from_address_id: address1.id,
ignore_deny_list: false,
data: <<~EMAIL
From: foo@bar.com
To: foo@example.com
Expand All @@ -194,6 +197,7 @@

email = key_app.emails.create!(
from_address_id: from.id,
ignore_deny_list: false,
data: "To: #{to.text}\nSubject: #{subject}\n\n#{body}\n"
)

Expand Down
1 change: 1 addition & 0 deletions lib/api/deliveries/show.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ query($id: ID!) {
}
opened
clicked
ignoreDenyList
deliveryEvents {
time
dsn
Expand Down
15 changes: 13 additions & 2 deletions lib/cuttlefish_smtp_server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -165,17 +165,28 @@ def process_data_line(line)
end
end

IGNORE_DENY_LIST_HEADER = "X-Cuttlefish-Ignore-Deny-List"

def receive_message
current.received = true
current.completed_at = Time.now

# TODO: No need to capture current.sender, current.received,
# current.completed_at because we're not passing it on

# Now check for special headers
m = Mail.new(current.data)
h = m.header[IGNORE_DENY_LIST_HEADER]
ignore_deny_list = (!h.nil? && h.value == "true")

# Remove header
m.header[IGNORE_DENY_LIST_HEADER] = nil

email = Email.create!(
to: current.recipients,
data: current.data,
app_id: current.app_id
data: m.to_s,
app_id: current.app_id,
ignore_deny_list: ignore_deny_list
)

SendEmailWorker.perform_async(email.id)
Expand Down
1 change: 1 addition & 0 deletions spec/factories/emails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
FactoryBot.define do
factory :email do
app
ignore_deny_list { false }
end
end
112 changes: 82 additions & 30 deletions spec/lib/cuttlefish_smtp_server_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,37 +97,89 @@
end
end
describe "#receive_message" do
it do
allow(EmailServices::Send).to receive(:call)
data = [
"MIME-Version: 1.0",
"Content-Type: text/plain; charset=\"utf-8\"",
"Content-Transfer-Encoding: 8bit",
"Subject: [WriteIT] Message: asdasd",
"From: Felipe <felipe@fiera-feroz.cl>, " \
"Matthew <matthew@fiera-feroz.cl>",
"To: felipe@fiera-feroz.cl",
"Date: Fri, 13 Mar 2015 14:42:20 -0000",
"Message-ID: <20150313144220.12848.46019@paro-taktsang>",
"",
"Contra toda autoridad!...excepto mi mamá!"
].join("\r\n")
# Simulate the encoding that we would assume when the data is received
# over the wire so to speak
data.force_encoding("ASCII-8BIT")
connection.receive_sender("ciudadanoi@email.org")
connection.receive_recipient("Felipe <felipe@fiera-feroz.cl>")
connection.receive_recipient("Matthew <matthew@fiera-feroz.cl>")
connection.receive_plain_auth(app.smtp_username, app.smtp_password)
connection.current.data = data
Sidekiq::Testing.inline! do
connection.receive_message
context "message with UTF8 encoding" do
let(:data) do
data = [
"MIME-Version: 1.0",
"Content-Type: text/plain; charset=\"utf-8\"",
"Content-Transfer-Encoding: 8bit",
"Subject: [WriteIT] Message: asdasd",
"From: Felipe <felipe@fiera-feroz.cl>, " \
"Matthew <matthew@fiera-feroz.cl>",
"To: felipe@fiera-feroz.cl",
"Date: Fri, 13 Mar 2015 14:42:20 -0000",
"Message-ID: <20150313144220.12848.46019@paro-taktsang>",
"",
"Contra toda autoridad!...excepto mi mamá!"
].join("\r\n")
# Simulate the encoding that we would assume when the data is received
# over the wire so to speak
data.force_encoding("ASCII-8BIT")
data
end

it do
allow(EmailServices::Send).to receive(:call)
connection.receive_sender("ciudadanoi@email.org")
connection.receive_recipient("Felipe <felipe@fiera-feroz.cl>")
connection.receive_recipient("Matthew <matthew@fiera-feroz.cl>")
connection.receive_plain_auth(app.smtp_username, app.smtp_password)
connection.current.data = data
Sidekiq::Testing.inline! do
connection.receive_message
end
expect(Email.count).to eq 1
mail = Email.first
expect(Mail.new(mail.data).decoded).to eq(
"Contra toda autoridad!...excepto mi mamá!"
)
expect(mail.ignore_deny_list).to be false
end
end

context "message with special header" do
let(:data) do
[
"Date: Wed, 12 Aug 2020 06:25:22 +0000",
"From: foo@bar.com",
"To: wibble@wobble.com",
"X-Cuttlefish-Ignore-Deny-List: true",
"Message-ID: <1.mail>",
"Subject: Hello",
"Mime-Version: 1.0",
"Content-Type: text/plain;",
" charset=UTF-8",
"Content-Transfer-Encoding: 7bit",
"",
"Hello!"
].join("\r\n")
end

it do
connection.receive_recipient("<wibble@wobble.com>")
connection.receive_plain_auth(app.smtp_username, app.smtp_password)
connection.current.data = data
Sidekiq::Testing.inline! do
connection.receive_message
end
expect(Email.count).to eq 1
mail = Email.first
expect(mail.ignore_deny_list).to be true
# The header should have been removed
expect(mail.data).to eq [
"Date: Wed, 12 Aug 2020 06:25:22 +0000",
"From: foo@bar.com",
"To: wibble@wobble.com",
"Message-ID: <1.mail>",
"Subject: Hello",
"Mime-Version: 1.0",
"Content-Type: text/plain;",
" charset=UTF-8",
"Content-Transfer-Encoding: 7bit",
"",
"Hello!"
].join("\r\n")
end
expect(Email.count).to eq 1
mail = Email.first
expect(Mail.new(mail.data).decoded).to eq(
"Contra toda autoridad!...excepto mi mamá!"
)
end
end
end
5 changes: 1 addition & 4 deletions spec/lib/filters/master_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@
end

it do
app = App.create!(name: "Test")
email = Email.create!(app_id: app.id)
delivery = Delivery.create!(email: email, app: app)
mail2 = Filters::Master.new(delivery: delivery).filter_mail(mail)
mail2 = Filters::Master.new(delivery: create(:delivery)).filter_mail(mail)
expect(Nokogiri::HTML(mail2.html_part.decoded).at("p").inner_text).to eq(
"vašem"
)
Expand Down

0 comments on commit a97ecb8

Please sign in to comment.