From cbe418ec593442cc6d59707b6831b1f4a8070dbf Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Sat, 7 Dec 2024 19:10:02 +0100 Subject: [PATCH] try clarifying that aliases are lists, not to be used for simply adding an address to an account for issue #244 by exander77 --- doc.go | 18 +++++++++++------- main.go | 19 ++++++++++++------- webadmin/admin.js | 12 ++++++++---- webadmin/admin.ts | 13 +++++++++---- 4 files changed, 40 insertions(+), 22 deletions(-) diff --git a/doc.go b/doc.go index ce2b22533..cb6d1b5ec 100644 --- a/doc.go +++ b/doc.go @@ -1056,25 +1056,29 @@ error too, for reference. # mox config alias list -List aliases for domain. +Show aliases (lists) for domain. usage: mox config alias list domain # mox config alias print -Print settings and members of alias. +Print settings and members of alias (list). usage: mox config alias print alias # mox config alias add -Add new alias with one or more addresses and public posting enabled. +Add new alias (list) with one or more addresses and public posting enabled. + +An alias is used for delivering incoming email to multiple recipients. If you +want to add an address to an account, don't use an alias, just add the address +to the account. usage: mox config alias add alias@domain rcpt1@domain ... # mox config alias update -Update alias configuration. +Update alias (list) configuration. usage: mox config alias update alias@domain [-postpublic false|true -listmembers false|true -allowmsgfrom false|true] -allowmsgfrom string @@ -1086,19 +1090,19 @@ Update alias configuration. # mox config alias rm -Remove alias. +Remove alias (list). usage: mox config alias rm alias@domain # mox config alias addaddr -Add addresses to alias. +Add addresses to alias (list). usage: mox config alias addaddr alias@domain rcpt1@domain ... # mox config alias rmaddr -Remove addresses from alias. +Remove addresses from alias (list). usage: mox config alias rmaddr alias@domain rcpt1@domain ... diff --git a/main.go b/main.go index 57c586e77..2a62f4f1a 100644 --- a/main.go +++ b/main.go @@ -737,7 +737,7 @@ func ctlcmdConfigDomainRemove(ctl *ctl, d dns.Domain) { func cmdConfigAliasList(c *cmd) { c.params = "domain" - c.help = `List aliases for domain.` + c.help = `Show aliases (lists) for domain.` args := c.Parse() if len(args) != 1 { c.Usage() @@ -756,7 +756,7 @@ func ctlcmdConfigAliasList(ctl *ctl, address string) { func cmdConfigAliasPrint(c *cmd) { c.params = "alias" - c.help = `Print settings and members of alias.` + c.help = `Print settings and members of alias (list).` args := c.Parse() if len(args) != 1 { c.Usage() @@ -775,7 +775,12 @@ func ctlcmdConfigAliasPrint(ctl *ctl, address string) { func cmdConfigAliasAdd(c *cmd) { c.params = "alias@domain rcpt1@domain ..." - c.help = `Add new alias with one or more addresses and public posting enabled.` + c.help = `Add new alias (list) with one or more addresses and public posting enabled. + +An alias is used for delivering incoming email to multiple recipients. If you +want to add an address to an account, don't use an alias, just add the address +to the account. +` args := c.Parse() if len(args) < 2 { c.Usage() @@ -796,7 +801,7 @@ func ctlcmdConfigAliasAdd(ctl *ctl, address string, alias config.Alias) { func cmdConfigAliasUpdate(c *cmd) { c.params = "alias@domain [-postpublic false|true -listmembers false|true -allowmsgfrom false|true]" - c.help = `Update alias configuration.` + c.help = `Update alias (list) configuration.` var postpublic, listmembers, allowmsgfrom string c.flag.StringVar(&postpublic, "postpublic", "", "whether anyone or only list members can post") c.flag.StringVar(&listmembers, "listmembers", "", "whether list members can list members") @@ -822,7 +827,7 @@ func ctlcmdConfigAliasUpdate(ctl *ctl, alias, postpublic, listmembers, allowmsgf func cmdConfigAliasRemove(c *cmd) { c.params = "alias@domain" - c.help = "Remove alias." + c.help = "Remove alias (list)." args := c.Parse() if len(args) != 1 { c.Usage() @@ -840,7 +845,7 @@ func ctlcmdConfigAliasRemove(ctl *ctl, alias string) { func cmdConfigAliasAddaddr(c *cmd) { c.params = "alias@domain rcpt1@domain ..." - c.help = `Add addresses to alias.` + c.help = `Add addresses to alias (list).` args := c.Parse() if len(args) < 2 { c.Usage() @@ -859,7 +864,7 @@ func ctlcmdConfigAliasAddaddr(ctl *ctl, alias string, addresses []string) { func cmdConfigAliasRemoveaddr(c *cmd) { c.params = "alias@domain rcpt1@domain ..." - c.help = `Remove addresses from alias.` + c.help = `Remove addresses from alias (list).` args := c.Parse() if len(args) < 2 { c.Usage() diff --git a/webadmin/admin.js b/webadmin/admin.js index 0435e66bd..fb9e5a449 100644 --- a/webadmin/admin.js +++ b/webadmin/admin.js @@ -2175,7 +2175,7 @@ const account = async (name) => { await check(fieldset, client.AddressAdd(address, name)); form.reset(); window.location.reload(); // todo: only reload the destinations - }, fieldset = dom.fieldset(dom.label(style({ display: 'inline-block' }), dom.span('Localpart', attr.title('The localpart is the part before the "@"-sign of an email address. If empty, a catchall address is configured for the domain.')), dom.br(), localpart = dom.input()), '@', dom.label(style({ display: 'inline-block' }), dom.span('Domain'), dom.br(), domain = dom.select((domains || []).map(d => dom.option(domainName(d), domainName(d) === config.Domain ? attr.selected('') : [])))), ' ', dom.submitbutton('Add address'))), dom.br(), dom.h2('Aliases/lists'), dom.table(dom.thead(dom.tr(dom.th('Alias address', attr.title('Messages sent to this address will be delivered to all members of the alias/list. A member does not receive a message if their address is in the message From header.')), dom.th('Subscription address'), dom.th('Allowed senders', attr.title('Whether only members can send through the alias/list, or anyone.')), dom.th('Send as alias address', attr.title('If enabled, messages can be sent with the alias address in the message "From" header.')), dom.th('Members visible', attr.title('If enabled, members can see the addresses of other members.')))), (config.Aliases || []).length === 0 ? dom.tr(dom.td(attr.colspan('6'), 'None')) : [], (config.Aliases || []).sort((a, b) => a.Alias.LocalpartStr < b.Alias.LocalpartStr ? -1 : (domainName(a.Alias.Domain) < domainName(b.Alias.Domain) ? -1 : 1)).map(a => dom.tr(dom.td(dom.a(prewrap(a.Alias.LocalpartStr, '@', domainName(a.Alias.Domain)), attr.href('#domains/' + domainName(a.Alias.Domain) + '/alias/' + encodeURIComponent(a.Alias.LocalpartStr)))), dom.td(prewrap(a.SubscriptionAddress)), dom.td(a.Alias.PostPublic ? 'Anyone' : 'Members only'), dom.td(a.Alias.AllowMsgFrom ? 'Yes' : 'No'), dom.td(a.Alias.ListMembers ? 'Yes' : 'No'), dom.td(dom.clickbutton('Remove', async function click(e) { + }, fieldset = dom.fieldset(dom.label(style({ display: 'inline-block' }), dom.span('Localpart', attr.title('The localpart is the part before the "@"-sign of an email address. If empty, a catchall address is configured for the domain.')), dom.br(), localpart = dom.input()), '@', dom.label(style({ display: 'inline-block' }), dom.span('Domain'), dom.br(), domain = dom.select((domains || []).map(d => dom.option(domainName(d), domainName(d) === config.Domain ? attr.selected('') : [])))), ' ', dom.submitbutton('Add address'))), dom.br(), dom.h2('Alias (list) membership'), dom.table(dom.thead(dom.tr(dom.th('Alias address', attr.title('Messages sent to this address will be delivered to all members of the alias/list. A member does not receive a message if their address is in the message From header.')), dom.th('Subscription address'), dom.th('Allowed senders', attr.title('Whether only members can send through the alias/list, or anyone.')), dom.th('Send as alias address', attr.title('If enabled, messages can be sent with the alias address in the message "From" header.')), dom.th('Members visible', attr.title('If enabled, members can see the addresses of other members.')))), (config.Aliases || []).length === 0 ? dom.tr(dom.td(attr.colspan('6'), 'None')) : [], (config.Aliases || []).sort((a, b) => a.Alias.LocalpartStr < b.Alias.LocalpartStr ? -1 : (domainName(a.Alias.Domain) < domainName(b.Alias.Domain) ? -1 : 1)).map(a => dom.tr(dom.td(dom.a(prewrap(a.Alias.LocalpartStr, '@', domainName(a.Alias.Domain)), attr.href('#domains/' + domainName(a.Alias.Domain) + '/alias/' + encodeURIComponent(a.Alias.LocalpartStr)))), dom.td(prewrap(a.SubscriptionAddress)), dom.td(a.Alias.PostPublic ? 'Anyone' : 'Members only'), dom.td(a.Alias.AllowMsgFrom ? 'Yes' : 'No'), dom.td(a.Alias.ListMembers ? 'Yes' : 'No'), dom.td(dom.clickbutton('Remove', async function click(e) { await check(e.target, client.AliasAddressesRemove(a.Alias.LocalpartStr, domainName(a.Alias.Domain), [a.SubscriptionAddress])); window.location.reload(); // todo: reload less }))))), dom.br(), dom.h2('Settings'), dom.form(fieldsetSettings = dom.fieldset(dom.label(style({ display: 'block', marginBottom: '.5ex' }), dom.span('Maximum outgoing messages per day', attr.title('Maximum number of outgoing messages for this account in a 24 hour window. This limits the damage to recipients and the reputation of this mail server in case of account compromise. Default 1000. MaxOutgoingMessagesPerDay in configuration file.')), dom.br(), maxOutgoingMessagesPerDay = dom.input(attr.type('number'), attr.required(''), attr.value('' + (config.MaxOutgoingMessagesPerDay || 1000)))), dom.label(style({ display: 'block', marginBottom: '.5ex' }), dom.span('Maximum first-time recipients per day', attr.title('Maximum number of first-time recipients in outgoing messages for this account in a 24 hour window. This limits the damage to recipients and the reputation of this mail server in case of account compromise. Default 200. MaxFirstTimeRecipientsPerDay in configuration file.')), dom.br(), maxFirstTimeRecipientsPerDay = dom.input(attr.type('number'), attr.required(''), attr.value('' + (config.MaxFirstTimeRecipientsPerDay || 200)))), dom.label(style({ display: 'block', marginBottom: '.5ex' }), dom.span('Disk usage quota: Maximum total message size ', attr.title('Default maximum total message size in bytes for the account, overriding any globally configured default maximum size if non-zero. A negative value can be used to have no limit in case there is a limit by default. Attempting to add new messages to an account beyond its maximum total size will result in an error. Useful to prevent a single account from filling storage. Use units "k" for kilobytes, or "m", "g", "t".')), dom.br(), quotaMessageSize = dom.input(attr.value(formatQuotaSize(config.QuotaMessageSize))), ' Current usage is ', formatQuotaSize(Math.floor(diskUsage / (1024 * 1024)) * 1024 * 1024), '.'), dom.div(style({ display: 'block', marginBottom: '.5ex' }), dom.label(firstTimeSenderDelay = dom.input(attr.type('checkbox'), config.NoFirstTimeSenderDelay ? [] : attr.checked('')), ' ', dom.span('Delay deliveries from first-time senders.', attr.title('To slow down potential spammers, when the message is misclassified as non-junk. Turning off the delay can be useful when the account processes messages automatically and needs fast responses.')))), dom.submitbutton('Save')), async function submit(e) { @@ -2289,6 +2289,7 @@ const domain = async (d) => { let aliasFieldset; let aliasLocalpart; let aliasAddresses; + let aliasAddText; let descrFieldset; let descrText; let clientSettingsDomainFieldset; @@ -2364,9 +2365,9 @@ const domain = async (d) => { await check(addrFieldset, client.AddressAdd(addrLocalpart.value + '@' + d, addrAccount.value)); addrForm.reset(); window.location.reload(); // todo: only reload the addresses - }, addrFieldset = dom.fieldset(dom.label(style({ display: 'inline-block' }), dom.span('Localpart', attr.title('The localpart is the part before the "@"-sign of an address. An empty localpart is the catchall destination/address for the domain.')), dom.br(), addrLocalpart = dom.input()), '@', domainName(dnsdomain), ' ', dom.label(style({ display: 'inline-block' }), dom.span('Account', attr.title('Account to assign the address to.')), dom.br(), addrAccount = dom.select(attr.required(''), (accounts || []).map(a => dom.option(a)))), ' ', dom.submitbutton('Add address', attr.title('Address will be added and the config reloaded.')))), dom.br(), dom.h2('Aliases/lists'), dom.table(dom.thead(dom.tr(dom.th('Address'), dom.th('Allowed senders', attr.title('Whether only members can send through the alias/list, or anyone.')), dom.th('Send as alias address', attr.title('If enabled, messages can be sent with the alias address in the message "From" header.')), dom.th('Members visible', attr.title('If enabled, members can see the addresses of other members.')))), Object.values(localpartAliases).length === 0 ? dom.tr(dom.td(attr.colspan('4'), 'None')) : [], Object.values(localpartAliases).sort((a, b) => a.LocalpartStr < b.LocalpartStr ? -1 : 1).map(a => { + }, addrFieldset = dom.fieldset(dom.label(style({ display: 'inline-block' }), dom.span('Localpart', attr.title('The localpart is the part before the "@"-sign of an address. An empty localpart is the catchall destination/address for the domain.')), dom.br(), addrLocalpart = dom.input()), '@', domainName(dnsdomain), ' ', dom.label(style({ display: 'inline-block' }), dom.span('Account', attr.title('Account to assign the address to.')), dom.br(), addrAccount = dom.select(attr.required(''), (accounts || []).map(a => dom.option(a)))), ' ', dom.submitbutton('Add address', attr.title('Address will be added and the config reloaded.')))), dom.br(), dom.h2('Aliases (lists)'), dom.table(dom.thead(dom.tr(dom.th('Address'), dom.th('Allowed senders', attr.title('Whether only members can send through the alias/list, or anyone.')), dom.th('Send as alias address', attr.title('If enabled, messages can be sent with the alias address in the message "From" header.')), dom.th('Members visible', attr.title('If enabled, members can see the addresses of other members.')))), Object.values(localpartAliases).length === 0 ? dom.tr(dom.td(attr.colspan('4'), 'None')) : [], Object.values(localpartAliases).sort((a, b) => a.LocalpartStr < b.LocalpartStr ? -1 : 1).map(a => { return dom.tr(dom.td(dom.a(prewrap(a.LocalpartStr), attr.href('#domains/' + d + '/alias/' + encodeURIComponent(a.LocalpartStr)))), dom.td(a.PostPublic ? 'Anyone' : 'Members only'), dom.td(a.AllowMsgFrom ? 'Yes' : 'No'), dom.td(a.ListMembers ? 'Yes' : 'No')); - })), dom.br(), dom.h2('Add alias'), dom.form(async function submit(e) { + })), dom.br(), dom.h2('Add alias (list)'), dom.form(async function submit(e) { e.preventDefault(); e.stopPropagation(); const alias = { @@ -2380,7 +2381,10 @@ const domain = async (d) => { }; await check(aliasFieldset, client.AliasAdd(aliasLocalpart.value, d, alias)); window.location.hash = '#domains/' + d + '/alias/' + encodeURIComponent(aliasLocalpart.value); - }, aliasFieldset = dom.fieldset(style({ display: 'flex', alignItems: 'flex-start', gap: '1em' }), dom.label(dom.div('Localpart', attr.title('The localpart is the part before the "@"-sign of an address.')), aliasLocalpart = dom.input(attr.required('')), '@', domainName(dnsdomain), ' '), dom.label(dom.div('Addresses', attr.title('One members address per line, full address of form localpart@domain. At least one address required.')), aliasAddresses = dom.textarea(attr.required(''), attr.rows('1'), function focus() { aliasAddresses.setAttribute('rows', '5'); })), dom.div(dom.div('\u00a0'), dom.submitbutton('Add alias', attr.title('Alias will be added and the config reloaded.'))))), dom.br(), RoutesEditor('domain-specific', transports, domainConfig.Routes || [], async (routes) => await client.DomainRoutesSave(d, routes)), dom.br(), dom.h2('Settings'), dom.form(async function submit(e) { + }, aliasFieldset = dom.fieldset(style({ display: 'flex', alignItems: 'flex-start', gap: '1em' }), dom.label(dom.div('Localpart', attr.title('The localpart is the part before the "@"-sign of an address.')), aliasLocalpart = dom.input(attr.required('')), '@', domainName(dnsdomain), ' '), dom.label(dom.div('Addresses', attr.title('One members address per line, full address of form localpart@domain. At least one address required.')), aliasAddresses = dom.textarea(attr.required(''), attr.rows('1'), function focus() { + aliasAddresses.setAttribute('rows', '5'); + aliasAddText.style.visibility = 'visible'; + })), dom.div(dom.div('\u00a0'), dom.submitbutton('Add alias', attr.title('Alias will be added and the config reloaded.')), aliasAddText = dom.p(style({ visibility: 'hidden', fontStyle: 'italic' }), 'Messages sent to aliases are delivered to each member address of the alias, like a mailing list. For an additional address for an account, add it as regular address (see above).')))), dom.br(), RoutesEditor('domain-specific', transports, domainConfig.Routes || [], async (routes) => await client.DomainRoutesSave(d, routes)), dom.br(), dom.h2('Settings'), dom.form(async function submit(e) { e.preventDefault(); e.stopPropagation(); await check(descrFieldset, client.DomainDescriptionSave(d, descrText.value)); diff --git a/webadmin/admin.ts b/webadmin/admin.ts index 8fdb2788b..b7133e55a 100644 --- a/webadmin/admin.ts +++ b/webadmin/admin.ts @@ -884,7 +884,7 @@ const account = async (name: string) => { ), dom.br(), - dom.h2('Aliases/lists'), + dom.h2('Alias (list) membership'), dom.table( dom.thead( dom.tr( @@ -1094,6 +1094,7 @@ const domain = async (d: string) => { let aliasFieldset: HTMLFieldSetElement let aliasLocalpart: HTMLInputElement let aliasAddresses: HTMLTextAreaElement + let aliasAddText: HTMLElement let descrFieldset: HTMLFieldSetElement let descrText: HTMLInputElement @@ -1347,7 +1348,7 @@ const domain = async (d: string) => { ), dom.br(), - dom.h2('Aliases/lists'), + dom.h2('Aliases (lists)'), dom.table( dom.thead( dom.tr( @@ -1368,7 +1369,7 @@ const domain = async (d: string) => { }), ), dom.br(), - dom.h2('Add alias'), + dom.h2('Add alias (list)'), dom.form( async function submit(e: SubmitEvent) { e.preventDefault() @@ -1395,11 +1396,15 @@ const domain = async (d: string) => { ), dom.label( dom.div('Addresses', attr.title('One members address per line, full address of form localpart@domain. At least one address required.')), - aliasAddresses=dom.textarea(attr.required(''), attr.rows('1'), function focus() { aliasAddresses.setAttribute('rows', '5') }), + aliasAddresses=dom.textarea(attr.required(''), attr.rows('1'), function focus() { + aliasAddresses.setAttribute('rows', '5') + aliasAddText.style.visibility = 'visible' + }), ), dom.div( dom.div('\u00a0'), dom.submitbutton('Add alias', attr.title('Alias will be added and the config reloaded.')), + aliasAddText=dom.p(style({visibility: 'hidden', fontStyle: 'italic'}), 'Messages sent to aliases are delivered to each member address of the alias, like a mailing list. For an additional address for an account, add it as regular address (see above).'), ), ), ),