FEATURE: Allow sending group SMTP emails with from alias (#15687)

This commit allows group SMTP emails to be sent with a
different from email address that has been set up as an
alias in the email provider. Emails from the alias will
be grouped correctly using Message-IDs in the mail client,
and replies to the alias go into the correct group inbox.
This commit is contained in:
Martin Brennan
2022-02-07 13:52:01 +10:00
committed by GitHub
parent 454d3740b4
commit 0a738bd5bc
14 changed files with 71 additions and 9 deletions

View File

@ -37,6 +37,7 @@ export default Component.extend({
EmberObject.create({ EmberObject.create({
email_username: this.group.email_username, email_username: this.group.email_username,
email_password: this.group.email_password, email_password: this.group.email_password,
email_from_alias: this.group.email_from_alias,
smtp_server: this.group.smtp_server, smtp_server: this.group.smtp_server,
smtp_port: (this.group.smtp_port || "").toString(), smtp_port: (this.group.smtp_port || "").toString(),
smtp_ssl: this.group.smtp_ssl, smtp_ssl: this.group.smtp_ssl,
@ -73,6 +74,7 @@ export default Component.extend({
smtp_port: this.form.smtp_port, smtp_port: this.form.smtp_port,
smtp_ssl: this.form.smtp_ssl, smtp_ssl: this.form.smtp_ssl,
email_username: this.form.email_username, email_username: this.form.email_username,
email_from_alias: this.form.email_from_alias,
email_password: this.form.email_password, email_password: this.form.email_password,
}); });
}) })

View File

@ -243,6 +243,7 @@ const Group = RestModel.extend({
imap_mailbox_name: this.imap_mailbox_name, imap_mailbox_name: this.imap_mailbox_name,
imap_enabled: this.imap_enabled, imap_enabled: this.imap_enabled,
email_username: this.email_username, email_username: this.email_username,
email_from_alias: this.email_from_alias,
email_password: this.email_password, email_password: this.email_password,
flair_icon: null, flair_icon: null,
flair_upload_id: null, flair_upload_id: null,

View File

@ -45,5 +45,5 @@
</div> </div>
<br> <br>
{{group-manage-save-button model=group disabled=(not emailSettingsValid) beforeSave=beforeSave afterSave=afterSave tabindex="14"}} {{group-manage-save-button model=group disabled=(not emailSettingsValid) beforeSave=beforeSave afterSave=afterSave tabindex="15"}}
</div> </div>

View File

@ -8,11 +8,11 @@
<div class="control-group"> <div class="control-group">
<label for="smtp_server">{{i18n "groups.manage.email.credentials.smtp_server"}}</label> <label for="smtp_server">{{i18n "groups.manage.email.credentials.smtp_server"}}</label>
{{input type="text" name="smtp_server" value=form.smtp_server tabindex="3" onChange=(action "resetSettingsValid")}} {{input type="text" name="smtp_server" value=form.smtp_server tabindex="4" onChange=(action "resetSettingsValid")}}
</div> </div>
<label for="enable_ssl"> <label for="enable_ssl">
{{input type="checkbox" checked=form.smtp_ssl id="enable_ssl" tabindex="5" onChange=(action "resetSettingsValid")}} {{input type="checkbox" checked=form.smtp_ssl id="enable_ssl" tabindex="6" onChange=(action "resetSettingsValid")}}
{{i18n "groups.manage.email.credentials.smtp_ssl"}} {{i18n "groups.manage.email.credentials.smtp_ssl"}}
</label> </label>
</div> </div>
@ -25,7 +25,15 @@
<div class="control-group"> <div class="control-group">
<label for="smtp_port">{{i18n "groups.manage.email.credentials.smtp_port"}}</label> <label for="smtp_port">{{i18n "groups.manage.email.credentials.smtp_port"}}</label>
{{input type="text" name="smtp_port" value=form.smtp_port tabindex="4" onChange=(action "resetSettingsValid" form.smtp_port)}} {{input type="text" name="smtp_port" value=form.smtp_port tabindex="5" onChange=(action "resetSettingsValid" form.smtp_port)}}
</div>
</div>
<div>
<div class="control-group">
<label for="from_alias">{{i18n "groups.manage.email.settings.from_alias"}}</label>
{{input type="text" name="from_alias" id="from_alias" value=form.email_from_alias onChange=(action "resetSettingsValid") tabindex="3"}}
<p>{{i18n "groups.manage.email.settings.from_alias_hint"}}</p>
</div> </div>
</div> </div>
</form> </form>
@ -43,7 +51,7 @@
action=(action "testSmtpSettings") action=(action "testSmtpSettings")
icon="cog" icon="cog"
label="groups.manage.email.test_settings" label="groups.manage.email.test_settings"
tabindex="6" tabindex="7"
title="groups.manage.email.settings_required" title="groups.manage.email.settings_required"
}} }}

View File

@ -106,6 +106,8 @@ acceptance(
await fillIn('input[name="username"]', "myusername@gmail.com"); await fillIn('input[name="username"]', "myusername@gmail.com");
await fillIn('input[name="password"]', "password@gmail.com"); await fillIn('input[name="password"]', "password@gmail.com");
await fillIn("#from_alias", "akasomegroup@example.com");
await click(".test-smtp-settings"); await click(".test-smtp-settings");
assert.ok(exists(".smtp-settings-ok"), "tested settings are ok"); assert.ok(exists(".smtp-settings-ok"), "tested settings are ok");

View File

@ -252,7 +252,7 @@ table.group-category-permissions {
.group-imap-email-settings { .group-imap-email-settings {
.groups-form { .groups-form {
display: grid; display: grid;
grid-template-columns: 1fr 3fr; grid-template-columns: 1fr 1fr 1fr;
margin-bottom: 0; margin-bottom: 0;
&.groups-form-imap { &.groups-form-imap {

View File

@ -713,6 +713,7 @@ class GroupsController < ApplicationController
:imap_updated_at, :imap_updated_at,
:email_username, :email_username,
:email_password, :email_password,
:email_from_alias,
:primary_group, :primary_group,
:visibility_level, :visibility_level,
:members_visibility_level, :members_visibility_level,

View File

@ -48,7 +48,7 @@ class GroupSmtpMailer < ActionMailer::Base
add_re_to_subject: true, add_re_to_subject: true,
locale: SiteSetting.default_locale, locale: SiteSetting.default_locale,
delivery_method_options: delivery_options, delivery_method_options: delivery_options,
from: from_group.email_username, from: from_group.smtp_from_address,
from_alias: I18n.t('email_from_without_site', user_name: group_name), from_alias: I18n.t('email_from_without_site', user_name: group_name),
html_override: html_override(post), html_override: html_override(post),
cc: cc_addresses cc: cc_addresses

View File

@ -110,7 +110,8 @@ class Group < ActiveRecord::Base
"imap_port", "imap_port",
"imap_ssl", "imap_ssl",
"email_username", "email_username",
"email_password" "email_password",
"email_from_alias"
] ]
ALIAS_LEVELS = { ALIAS_LEVELS = {
@ -290,6 +291,10 @@ class Group < ActiveRecord::Base
end end
end end
def smtp_from_address
self.email_from_alias.present? ? self.email_from_alias : self.email_username
end
def downcase_incoming_email def downcase_incoming_email
self.incoming_email = (incoming_email || "").strip.downcase.presence self.incoming_email = (incoming_email || "").strip.downcase.presence
end end
@ -708,7 +713,9 @@ class Group < ActiveRecord::Base
def self.find_by_email(email) def self.find_by_email(email)
self.where( self.where(
"email_username = :email OR string_to_array(incoming_email, '|') @> ARRAY[:email]", "email_username = :email OR
string_to_array(incoming_email, '|') @> ARRAY[:email] OR
email_from_alias = :email",
email: Email.downcase(email) email: Email.downcase(email)
).first ).first
end end
@ -1128,6 +1135,7 @@ end
# imap_enabled :boolean default(FALSE) # imap_enabled :boolean default(FALSE)
# imap_updated_at :datetime # imap_updated_at :datetime
# imap_updated_by_id :integer # imap_updated_by_id :integer
# email_from_alias :string
# #
# Indexes # Indexes
# #

View File

@ -32,6 +32,7 @@ class GroupShowSerializer < BasicGroupSerializer
:imap_updated_by, :imap_updated_by,
:email_username, :email_username,
:email_password, :email_password,
:email_from_alias,
:imap_last_error, :imap_last_error,
:imap_old_emails, :imap_old_emails,
:imap_new_emails, :imap_new_emails,

View File

@ -754,6 +754,8 @@ en:
title: "Settings" title: "Settings"
allow_unknown_sender_topic_replies: "Allow unknown sender topic replies." allow_unknown_sender_topic_replies: "Allow unknown sender topic replies."
allow_unknown_sender_topic_replies_hint: "Allows unknown senders to reply to group topics. If this is not enabled, replies from email addresses not already invited to the topic will create a new topic." allow_unknown_sender_topic_replies_hint: "Allows unknown senders to reply to group topics. If this is not enabled, replies from email addresses not already invited to the topic will create a new topic."
from_alias: "From Alias"
from_alias_hint: "Alias to use as the from address when sending group SMTP emails. Note this may not be supported by all mail providers, please consult your mail provider's documentation."
mailboxes: mailboxes:
synchronized: "Synchronized Mailbox" synchronized: "Synchronized Mailbox"
none_found: "No mailboxes were found in this email account." none_found: "No mailboxes were found in this email account."

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddEmailFromAliasToGroups < ActiveRecord::Migration[6.1]
def change
add_column :groups, :email_from_alias, :string, null: true
end
end

View File

@ -1225,6 +1225,23 @@ describe Group do
expect(group.smtp_updated_by).to eq(user) expect(group.smtp_updated_by).to eq(user)
end end
it "records the change for singular setting changes" do
group.update(
smtp_port: 587,
smtp_ssl: true,
smtp_server: "smtp.gmail.com",
email_username: "test@gmail.com",
email_password: "password",
)
group.record_email_setting_changes!(user)
group.reload
old_updated_at = group.smtp_updated_at
group.update(email_from_alias: "somealias@gmail.com")
group.record_email_setting_changes!(user)
expect(group.reload.smtp_updated_at).not_to eq_time(old_updated_at)
end
it "enables imap and records the change" do it "enables imap and records the change" do
group.update( group.update(
imap_port: 587, imap_port: 587,
@ -1314,5 +1331,12 @@ describe Group do
expect(Group.find_by_email("support@test.com")).to eq(group) expect(Group.find_by_email("support@test.com")).to eq(group)
expect(Group.find_by_email("nope@test.com")).to eq(nil) expect(Group.find_by_email("nope@test.com")).to eq(nil)
end end
it "finds the group by its email_from_alias" do
group.update!(email_username: "abc@test.com", email_from_alias: "somealias@test.com")
expect(Group.find_by_email("abc@test.com")).to eq(group)
expect(Group.find_by_email("somealias@test.com")).to eq(group)
expect(Group.find_by_email("nope@test.com")).to eq(nil)
end
end end
end end

View File

@ -221,6 +221,12 @@
"null" "null"
] ]
}, },
"email_from_alias": {
"type": [
"string",
"null"
]
},
"email_password": { "email_password": {
"type": [ "type": [
"string", "string",