Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
ae44a943
Commit
ae44a943
authored
Mar 15, 2026
by
shaw
Browse files
fix: 重置密码功能新增UI配置发送邮件域名
parent
8321e4a6
Changes
11
Hide whitespace changes
Inline
Side-by-side
backend/internal/handler/admin/setting_handler.go
View file @
ae44a943
...
@@ -80,6 +80,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
...
@@ -80,6 +80,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
RegistrationEmailSuffixWhitelist
:
settings
.
RegistrationEmailSuffixWhitelist
,
RegistrationEmailSuffixWhitelist
:
settings
.
RegistrationEmailSuffixWhitelist
,
PromoCodeEnabled
:
settings
.
PromoCodeEnabled
,
PromoCodeEnabled
:
settings
.
PromoCodeEnabled
,
PasswordResetEnabled
:
settings
.
PasswordResetEnabled
,
PasswordResetEnabled
:
settings
.
PasswordResetEnabled
,
FrontendURL
:
settings
.
FrontendURL
,
InvitationCodeEnabled
:
settings
.
InvitationCodeEnabled
,
InvitationCodeEnabled
:
settings
.
InvitationCodeEnabled
,
TotpEnabled
:
settings
.
TotpEnabled
,
TotpEnabled
:
settings
.
TotpEnabled
,
TotpEncryptionKeyConfigured
:
h
.
settingService
.
IsTotpEncryptionKeyConfigured
(),
TotpEncryptionKeyConfigured
:
h
.
settingService
.
IsTotpEncryptionKeyConfigured
(),
...
@@ -137,6 +138,7 @@ type UpdateSettingsRequest struct {
...
@@ -137,6 +138,7 @@ type UpdateSettingsRequest struct {
RegistrationEmailSuffixWhitelist
[]
string
`json:"registration_email_suffix_whitelist"`
RegistrationEmailSuffixWhitelist
[]
string
`json:"registration_email_suffix_whitelist"`
PromoCodeEnabled
bool
`json:"promo_code_enabled"`
PromoCodeEnabled
bool
`json:"promo_code_enabled"`
PasswordResetEnabled
bool
`json:"password_reset_enabled"`
PasswordResetEnabled
bool
`json:"password_reset_enabled"`
FrontendURL
string
`json:"frontend_url"`
InvitationCodeEnabled
bool
`json:"invitation_code_enabled"`
InvitationCodeEnabled
bool
`json:"invitation_code_enabled"`
TotpEnabled
bool
`json:"totp_enabled"`
// TOTP 双因素认证
TotpEnabled
bool
`json:"totp_enabled"`
// TOTP 双因素认证
...
@@ -326,6 +328,15 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
...
@@ -326,6 +328,15 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
}
}
}
}
// Frontend URL 验证
req
.
FrontendURL
=
strings
.
TrimSpace
(
req
.
FrontendURL
)
if
req
.
FrontendURL
!=
""
{
if
err
:=
config
.
ValidateAbsoluteHTTPURL
(
req
.
FrontendURL
);
err
!=
nil
{
response
.
BadRequest
(
c
,
"Frontend URL must be an absolute http(s) URL"
)
return
}
}
// 自定义菜单项验证
// 自定义菜单项验证
const
(
const
(
maxCustomMenuItems
=
20
maxCustomMenuItems
=
20
...
@@ -437,6 +448,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
...
@@ -437,6 +448,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
RegistrationEmailSuffixWhitelist
:
req
.
RegistrationEmailSuffixWhitelist
,
RegistrationEmailSuffixWhitelist
:
req
.
RegistrationEmailSuffixWhitelist
,
PromoCodeEnabled
:
req
.
PromoCodeEnabled
,
PromoCodeEnabled
:
req
.
PromoCodeEnabled
,
PasswordResetEnabled
:
req
.
PasswordResetEnabled
,
PasswordResetEnabled
:
req
.
PasswordResetEnabled
,
FrontendURL
:
req
.
FrontendURL
,
InvitationCodeEnabled
:
req
.
InvitationCodeEnabled
,
InvitationCodeEnabled
:
req
.
InvitationCodeEnabled
,
TotpEnabled
:
req
.
TotpEnabled
,
TotpEnabled
:
req
.
TotpEnabled
,
SMTPHost
:
req
.
SMTPHost
,
SMTPHost
:
req
.
SMTPHost
,
...
@@ -531,6 +543,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
...
@@ -531,6 +543,7 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
RegistrationEmailSuffixWhitelist
:
updatedSettings
.
RegistrationEmailSuffixWhitelist
,
RegistrationEmailSuffixWhitelist
:
updatedSettings
.
RegistrationEmailSuffixWhitelist
,
PromoCodeEnabled
:
updatedSettings
.
PromoCodeEnabled
,
PromoCodeEnabled
:
updatedSettings
.
PromoCodeEnabled
,
PasswordResetEnabled
:
updatedSettings
.
PasswordResetEnabled
,
PasswordResetEnabled
:
updatedSettings
.
PasswordResetEnabled
,
FrontendURL
:
updatedSettings
.
FrontendURL
,
InvitationCodeEnabled
:
updatedSettings
.
InvitationCodeEnabled
,
InvitationCodeEnabled
:
updatedSettings
.
InvitationCodeEnabled
,
TotpEnabled
:
updatedSettings
.
TotpEnabled
,
TotpEnabled
:
updatedSettings
.
TotpEnabled
,
TotpEncryptionKeyConfigured
:
h
.
settingService
.
IsTotpEncryptionKeyConfigured
(),
TotpEncryptionKeyConfigured
:
h
.
settingService
.
IsTotpEncryptionKeyConfigured
(),
...
@@ -614,6 +627,9 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
...
@@ -614,6 +627,9 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
if
before
.
PasswordResetEnabled
!=
after
.
PasswordResetEnabled
{
if
before
.
PasswordResetEnabled
!=
after
.
PasswordResetEnabled
{
changed
=
append
(
changed
,
"password_reset_enabled"
)
changed
=
append
(
changed
,
"password_reset_enabled"
)
}
}
if
before
.
FrontendURL
!=
after
.
FrontendURL
{
changed
=
append
(
changed
,
"frontend_url"
)
}
if
before
.
TotpEnabled
!=
after
.
TotpEnabled
{
if
before
.
TotpEnabled
!=
after
.
TotpEnabled
{
changed
=
append
(
changed
,
"totp_enabled"
)
changed
=
append
(
changed
,
"totp_enabled"
)
}
}
...
...
backend/internal/handler/auth_handler.go
View file @
ae44a943
...
@@ -459,9 +459,9 @@ func (h *AuthHandler) ForgotPassword(c *gin.Context) {
...
@@ -459,9 +459,9 @@ func (h *AuthHandler) ForgotPassword(c *gin.Context) {
return
return
}
}
frontendBaseURL
:=
strings
.
TrimSpace
(
h
.
cfg
.
Server
.
FrontendURL
)
frontendBaseURL
:=
strings
.
TrimSpace
(
h
.
settingSvc
.
GetFrontendURL
(
c
.
Request
.
Context
())
)
if
frontendBaseURL
==
""
{
if
frontendBaseURL
==
""
{
slog
.
Error
(
"
server.
frontend_url not configured; cannot build password reset link"
)
slog
.
Error
(
"frontend_url not configured
in settings or config
; cannot build password reset link"
)
response
.
InternalError
(
c
,
"Password reset is not configured"
)
response
.
InternalError
(
c
,
"Password reset is not configured"
)
return
return
}
}
...
...
backend/internal/handler/dto/settings.go
View file @
ae44a943
...
@@ -22,6 +22,7 @@ type SystemSettings struct {
...
@@ -22,6 +22,7 @@ type SystemSettings struct {
RegistrationEmailSuffixWhitelist
[]
string
`json:"registration_email_suffix_whitelist"`
RegistrationEmailSuffixWhitelist
[]
string
`json:"registration_email_suffix_whitelist"`
PromoCodeEnabled
bool
`json:"promo_code_enabled"`
PromoCodeEnabled
bool
`json:"promo_code_enabled"`
PasswordResetEnabled
bool
`json:"password_reset_enabled"`
PasswordResetEnabled
bool
`json:"password_reset_enabled"`
FrontendURL
string
`json:"frontend_url"`
InvitationCodeEnabled
bool
`json:"invitation_code_enabled"`
InvitationCodeEnabled
bool
`json:"invitation_code_enabled"`
TotpEnabled
bool
`json:"totp_enabled"`
// TOTP 双因素认证
TotpEnabled
bool
`json:"totp_enabled"`
// TOTP 双因素认证
TotpEncryptionKeyConfigured
bool
`json:"totp_encryption_key_configured"`
// TOTP 加密密钥是否已配置
TotpEncryptionKeyConfigured
bool
`json:"totp_encryption_key_configured"`
// TOTP 加密密钥是否已配置
...
...
backend/internal/server/api_contract_test.go
View file @
ae44a943
...
@@ -493,6 +493,7 @@ func TestAPIContracts(t *testing.T) {
...
@@ -493,6 +493,7 @@ func TestAPIContracts(t *testing.T) {
"registration_email_suffix_whitelist": [],
"registration_email_suffix_whitelist": [],
"promo_code_enabled": true,
"promo_code_enabled": true,
"password_reset_enabled": false,
"password_reset_enabled": false,
"frontend_url": "",
"totp_enabled": false,
"totp_enabled": false,
"totp_encryption_key_configured": false,
"totp_encryption_key_configured": false,
"smtp_host": "smtp.example.com",
"smtp_host": "smtp.example.com",
...
...
backend/internal/service/domain_constants.go
View file @
ae44a943
...
@@ -80,6 +80,7 @@ const (
...
@@ -80,6 +80,7 @@ const (
SettingKeyRegistrationEmailSuffixWhitelist
=
"registration_email_suffix_whitelist"
// 注册邮箱后缀白名单(JSON 数组)
SettingKeyRegistrationEmailSuffixWhitelist
=
"registration_email_suffix_whitelist"
// 注册邮箱后缀白名单(JSON 数组)
SettingKeyPromoCodeEnabled
=
"promo_code_enabled"
// 是否启用优惠码功能
SettingKeyPromoCodeEnabled
=
"promo_code_enabled"
// 是否启用优惠码功能
SettingKeyPasswordResetEnabled
=
"password_reset_enabled"
// 是否启用忘记密码功能(需要先开启邮件验证)
SettingKeyPasswordResetEnabled
=
"password_reset_enabled"
// 是否启用忘记密码功能(需要先开启邮件验证)
SettingKeyFrontendURL
=
"frontend_url"
// 前端基础URL,用于生成邮件中的重置密码链接
SettingKeyInvitationCodeEnabled
=
"invitation_code_enabled"
// 是否启用邀请码注册
SettingKeyInvitationCodeEnabled
=
"invitation_code_enabled"
// 是否启用邀请码注册
// 邮件服务设置
// 邮件服务设置
...
...
backend/internal/service/setting_service.go
View file @
ae44a943
...
@@ -116,6 +116,15 @@ func (s *SettingService) GetAllSettings(ctx context.Context) (*SystemSettings, e
...
@@ -116,6 +116,15 @@ func (s *SettingService) GetAllSettings(ctx context.Context) (*SystemSettings, e
return
s
.
parseSettings
(
settings
),
nil
return
s
.
parseSettings
(
settings
),
nil
}
}
// GetFrontendURL 获取前端基础URL(数据库优先,fallback 到配置文件)
func
(
s
*
SettingService
)
GetFrontendURL
(
ctx
context
.
Context
)
string
{
val
,
err
:=
s
.
settingRepo
.
GetValue
(
ctx
,
SettingKeyFrontendURL
)
if
err
==
nil
&&
strings
.
TrimSpace
(
val
)
!=
""
{
return
strings
.
TrimSpace
(
val
)
}
return
s
.
cfg
.
Server
.
FrontendURL
}
// GetPublicSettings 获取公开设置(无需登录)
// GetPublicSettings 获取公开设置(无需登录)
func
(
s
*
SettingService
)
GetPublicSettings
(
ctx
context
.
Context
)
(
*
PublicSettings
,
error
)
{
func
(
s
*
SettingService
)
GetPublicSettings
(
ctx
context
.
Context
)
(
*
PublicSettings
,
error
)
{
keys
:=
[]
string
{
keys
:=
[]
string
{
...
@@ -401,6 +410,7 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet
...
@@ -401,6 +410,7 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet
updates
[
SettingKeyRegistrationEmailSuffixWhitelist
]
=
string
(
registrationEmailSuffixWhitelistJSON
)
updates
[
SettingKeyRegistrationEmailSuffixWhitelist
]
=
string
(
registrationEmailSuffixWhitelistJSON
)
updates
[
SettingKeyPromoCodeEnabled
]
=
strconv
.
FormatBool
(
settings
.
PromoCodeEnabled
)
updates
[
SettingKeyPromoCodeEnabled
]
=
strconv
.
FormatBool
(
settings
.
PromoCodeEnabled
)
updates
[
SettingKeyPasswordResetEnabled
]
=
strconv
.
FormatBool
(
settings
.
PasswordResetEnabled
)
updates
[
SettingKeyPasswordResetEnabled
]
=
strconv
.
FormatBool
(
settings
.
PasswordResetEnabled
)
updates
[
SettingKeyFrontendURL
]
=
settings
.
FrontendURL
updates
[
SettingKeyInvitationCodeEnabled
]
=
strconv
.
FormatBool
(
settings
.
InvitationCodeEnabled
)
updates
[
SettingKeyInvitationCodeEnabled
]
=
strconv
.
FormatBool
(
settings
.
InvitationCodeEnabled
)
updates
[
SettingKeyTotpEnabled
]
=
strconv
.
FormatBool
(
settings
.
TotpEnabled
)
updates
[
SettingKeyTotpEnabled
]
=
strconv
.
FormatBool
(
settings
.
TotpEnabled
)
...
@@ -767,6 +777,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
...
@@ -767,6 +777,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
RegistrationEmailSuffixWhitelist
:
ParseRegistrationEmailSuffixWhitelist
(
settings
[
SettingKeyRegistrationEmailSuffixWhitelist
]),
RegistrationEmailSuffixWhitelist
:
ParseRegistrationEmailSuffixWhitelist
(
settings
[
SettingKeyRegistrationEmailSuffixWhitelist
]),
PromoCodeEnabled
:
settings
[
SettingKeyPromoCodeEnabled
]
!=
"false"
,
// 默认启用
PromoCodeEnabled
:
settings
[
SettingKeyPromoCodeEnabled
]
!=
"false"
,
// 默认启用
PasswordResetEnabled
:
emailVerifyEnabled
&&
settings
[
SettingKeyPasswordResetEnabled
]
==
"true"
,
PasswordResetEnabled
:
emailVerifyEnabled
&&
settings
[
SettingKeyPasswordResetEnabled
]
==
"true"
,
FrontendURL
:
settings
[
SettingKeyFrontendURL
],
InvitationCodeEnabled
:
settings
[
SettingKeyInvitationCodeEnabled
]
==
"true"
,
InvitationCodeEnabled
:
settings
[
SettingKeyInvitationCodeEnabled
]
==
"true"
,
TotpEnabled
:
settings
[
SettingKeyTotpEnabled
]
==
"true"
,
TotpEnabled
:
settings
[
SettingKeyTotpEnabled
]
==
"true"
,
SMTPHost
:
settings
[
SettingKeySMTPHost
],
SMTPHost
:
settings
[
SettingKeySMTPHost
],
...
...
backend/internal/service/settings_view.go
View file @
ae44a943
...
@@ -6,6 +6,7 @@ type SystemSettings struct {
...
@@ -6,6 +6,7 @@ type SystemSettings struct {
RegistrationEmailSuffixWhitelist
[]
string
RegistrationEmailSuffixWhitelist
[]
string
PromoCodeEnabled
bool
PromoCodeEnabled
bool
PasswordResetEnabled
bool
PasswordResetEnabled
bool
FrontendURL
string
InvitationCodeEnabled
bool
InvitationCodeEnabled
bool
TotpEnabled
bool
// TOTP 双因素认证
TotpEnabled
bool
// TOTP 双因素认证
...
...
frontend/src/api/admin/settings.ts
View file @
ae44a943
...
@@ -21,6 +21,7 @@ export interface SystemSettings {
...
@@ -21,6 +21,7 @@ export interface SystemSettings {
registration_email_suffix_whitelist
:
string
[]
registration_email_suffix_whitelist
:
string
[]
promo_code_enabled
:
boolean
promo_code_enabled
:
boolean
password_reset_enabled
:
boolean
password_reset_enabled
:
boolean
frontend_url
:
string
invitation_code_enabled
:
boolean
invitation_code_enabled
:
boolean
totp_enabled
:
boolean
// TOTP 双因素认证
totp_enabled
:
boolean
// TOTP 双因素认证
totp_encryption_key_configured
:
boolean
// TOTP 加密密钥是否已配置
totp_encryption_key_configured
:
boolean
// TOTP 加密密钥是否已配置
...
@@ -91,6 +92,7 @@ export interface UpdateSettingsRequest {
...
@@ -91,6 +92,7 @@ export interface UpdateSettingsRequest {
registration_email_suffix_whitelist
?:
string
[]
registration_email_suffix_whitelist
?:
string
[]
promo_code_enabled
?:
boolean
promo_code_enabled
?:
boolean
password_reset_enabled
?:
boolean
password_reset_enabled
?:
boolean
frontend_url
?:
string
invitation_code_enabled
?:
boolean
invitation_code_enabled
?:
boolean
totp_enabled
?:
boolean
// TOTP 双因素认证
totp_enabled
?:
boolean
// TOTP 双因素认证
default_balance
?:
number
default_balance
?:
number
...
...
frontend/src/i18n/locales/en.ts
View file @
ae44a943
...
@@ -3966,6 +3966,9 @@ export default {
...
@@ -3966,6 +3966,9 @@ export default {
invitationCodeHint
:
'
When enabled, users must enter a valid invitation code to register
'
,
invitationCodeHint
:
'
When enabled, users must enter a valid invitation code to register
'
,
passwordReset
:
'
Password Reset
'
,
passwordReset
:
'
Password Reset
'
,
passwordResetHint
:
'
Allow users to reset their password via email
'
,
passwordResetHint
:
'
Allow users to reset their password via email
'
,
frontendUrl
:
'
Frontend URL
'
,
frontendUrlPlaceholder
:
'
https://example.com
'
,
frontendUrlHint
:
'
Used to generate password reset links in emails. Example: https://example.com
'
,
totp
:
'
Two-Factor Authentication (2FA)
'
,
totp
:
'
Two-Factor Authentication (2FA)
'
,
totpHint
:
'
Allow users to use authenticator apps like Google Authenticator
'
,
totpHint
:
'
Allow users to use authenticator apps like Google Authenticator
'
,
totpKeyNotConfigured
:
totpKeyNotConfigured
:
...
...
frontend/src/i18n/locales/zh.ts
View file @
ae44a943
...
@@ -4140,6 +4140,9 @@ export default {
...
@@ -4140,6 +4140,9 @@ export default {
invitationCodeHint
:
'
开启后,用户注册时需要填写有效的邀请码
'
,
invitationCodeHint
:
'
开启后,用户注册时需要填写有效的邀请码
'
,
passwordReset
:
'
忘记密码
'
,
passwordReset
:
'
忘记密码
'
,
passwordResetHint
:
'
允许用户通过邮箱重置密码
'
,
passwordResetHint
:
'
允许用户通过邮箱重置密码
'
,
frontendUrl
:
'
前端地址
'
,
frontendUrlPlaceholder
:
'
https://example.com
'
,
frontendUrlHint
:
'
用于生成邮件中的密码重置链接,例如 https://example.com
'
,
totp
:
'
双因素认证 (2FA)
'
,
totp
:
'
双因素认证 (2FA)
'
,
totpHint
:
'
允许用户使用 Google Authenticator 等应用进行二次验证
'
,
totpHint
:
'
允许用户使用 Google Authenticator 等应用进行二次验证
'
,
totpKeyNotConfigured
:
totpKeyNotConfigured
:
...
...
frontend/src/views/admin/SettingsView.vue
View file @
ae44a943
...
@@ -653,6 +653,24 @@
...
@@ -653,6 +653,24 @@
<
/div
>
<
/div
>
<
Toggle
v
-
model
=
"
form.password_reset_enabled
"
/>
<
Toggle
v
-
model
=
"
form.password_reset_enabled
"
/>
<
/div
>
<
/div
>
<!--
Frontend
URL
-
Only
show
when
password
reset
is
enabled
-->
<
div
v
-
if
=
"
form.email_verify_enabled && form.password_reset_enabled
"
class
=
"
border-t border-gray-100 pt-4 dark:border-dark-700
"
>
<
label
class
=
"
mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300
"
>
{{
t
(
'
admin.settings.registration.frontendUrl
'
)
}}
<
/label
>
<
input
v
-
model
=
"
form.frontend_url
"
type
=
"
url
"
class
=
"
input
"
:
placeholder
=
"
t('admin.settings.registration.frontendUrlPlaceholder')
"
/>
<
p
class
=
"
mt-1.5 text-xs text-gray-500 dark:text-gray-400
"
>
{{
t
(
'
admin.settings.registration.frontendUrlHint
'
)
}}
<
/p
>
<
/div
>
<!--
TOTP
2
FA
-->
<!--
TOTP
2
FA
-->
<
div
<
div
...
@@ -1586,6 +1604,7 @@
...
@@ -1586,6 +1604,7 @@
<
/div
>
<
/div
>
<
Toggle
v
-
model
=
"
form.smtp_use_tls
"
/>
<
Toggle
v
-
model
=
"
form.smtp_use_tls
"
/>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
...
@@ -1820,6 +1839,7 @@ const form = reactive<SettingsForm>({
...
@@ -1820,6 +1839,7 @@ const form = reactive<SettingsForm>({
purchase_subscription_url
:
''
,
purchase_subscription_url
:
''
,
sora_client_enabled
:
false
,
sora_client_enabled
:
false
,
custom_menu_items
:
[]
as
Array
<
{
id
:
string
;
label
:
string
;
icon_svg
:
string
;
url
:
string
;
visibility
:
'
user
'
|
'
admin
'
;
sort_order
:
number
}
>
,
custom_menu_items
:
[]
as
Array
<
{
id
:
string
;
label
:
string
;
icon_svg
:
string
;
url
:
string
;
visibility
:
'
user
'
|
'
admin
'
;
sort_order
:
number
}
>
,
frontend_url
:
''
,
smtp_host
:
''
,
smtp_host
:
''
,
smtp_port
:
587
,
smtp_port
:
587
,
smtp_username
:
''
,
smtp_username
:
''
,
...
@@ -2097,6 +2117,7 @@ async function saveSettings() {
...
@@ -2097,6 +2117,7 @@ async function saveSettings() {
purchase_subscription_url
:
form
.
purchase_subscription_url
,
purchase_subscription_url
:
form
.
purchase_subscription_url
,
sora_client_enabled
:
form
.
sora_client_enabled
,
sora_client_enabled
:
form
.
sora_client_enabled
,
custom_menu_items
:
form
.
custom_menu_items
,
custom_menu_items
:
form
.
custom_menu_items
,
frontend_url
:
form
.
frontend_url
,
smtp_host
:
form
.
smtp_host
,
smtp_host
:
form
.
smtp_host
,
smtp_port
:
form
.
smtp_port
,
smtp_port
:
form
.
smtp_port
,
smtp_username
:
form
.
smtp_username
,
smtp_username
:
form
.
smtp_username
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment