mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 20:57:24 +00:00
feat: add reply-to header in email notification (#6393)
* feat: add reply-to header to smtp messages * fix: grpc reply_to_address min 0 and js var name * fix: add missing translations * fix merge and linting --------- Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
parent
9b43e28c23
commit
fd00ac533a
@ -688,6 +688,7 @@ DefaultInstance:
|
||||
# If the host of the sender is different from ExternalDomain set DefaultInstance.DomainPolicy.SMTPSenderAddressMatchesInstanceDomain to false
|
||||
From: # ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_FROM
|
||||
FromName: # ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_FROMNAME
|
||||
ReplyToAddress: # ZITADEL_DEFAULTINSTANCE_SMTPCONFIGURATION_SMTP_REPLYTOADDRESS
|
||||
MessageTexts:
|
||||
- MessageTextType: InitCode
|
||||
Language: de
|
||||
|
@ -15,12 +15,17 @@
|
||||
<form (ngSubmit)="savePolicy()" [formGroup]="form" autocomplete="off">
|
||||
<cnsl-form-field class="smtp-form-field" label="Sender Address" required="true">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.SENDERADDRESS' | translate }}</cnsl-label>
|
||||
<input cnslInput name="senderAddress" formControlName="senderAddress" />
|
||||
<input cnslInput name="senderAddress" formControlName="senderAddress" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="smtp-form-field" label="Sender Name" required="true">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.SENDERNAME' | translate }}</cnsl-label>
|
||||
<input cnslInput name="senderName" formControlName="senderName" />
|
||||
<input cnslInput name="senderName" formControlName="senderName" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="smtp-form-field" label="Reply-To Address">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.REPLYTOADDRESS' | translate }}</cnsl-label>
|
||||
<input cnslInput name="senderReplyToAddress" formControlName="replyToAddress" />
|
||||
</cnsl-form-field>
|
||||
|
||||
<mat-checkbox class="smtp-checkbox" formControlName="tls">
|
||||
@ -29,12 +34,12 @@
|
||||
|
||||
<cnsl-form-field class="smtp-form-field" label="Host And Port" required="true">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.HOSTANDPORT' | translate }}</cnsl-label>
|
||||
<input cnslInput name="hostAndPort" formControlName="hostAndPort" placeholder="smtp.mailtrap.io:2525" />
|
||||
<input cnslInput name="hostAndPort" formControlName="hostAndPort" placeholder="smtp.mailtrap.io:2525" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-form-field class="smtp-form-field" label="User" required="true">
|
||||
<cnsl-label>{{ 'SETTING.SMTP.USER' | translate }}</cnsl-label>
|
||||
<input id="smtp-user" cnslInput name="smtp-user" autocomplete="smtp-user" formControlName="user" />
|
||||
<input id="smtp-user" cnslInput name="smtp-user" autocomplete="smtp-user" formControlName="user" required />
|
||||
</cnsl-form-field>
|
||||
|
||||
<button
|
||||
|
@ -58,6 +58,7 @@ export class NotificationSettingsComponent implements OnInit {
|
||||
this.form = this.fb.group({
|
||||
senderAddress: [{ disabled: true, value: '' }, [requiredValidator]],
|
||||
senderName: [{ disabled: true, value: '' }, [requiredValidator]],
|
||||
replyToAddress: [{ disabled: true, value: '' }],
|
||||
tls: [{ disabled: true, value: true }, [requiredValidator]],
|
||||
hostAndPort: [{ disabled: true, value: '' }, [requiredValidator]],
|
||||
user: [{ disabled: true, value: '' }, [requiredValidator]],
|
||||
@ -143,6 +144,7 @@ export class NotificationSettingsComponent implements OnInit {
|
||||
req.setHost(this.hostAndPort?.value ?? '');
|
||||
req.setSenderAddress(this.senderAddress?.value ?? '');
|
||||
req.setSenderName(this.senderName?.value ?? '');
|
||||
req.setReplyToAddress(this.replyToAddress?.value ?? '');
|
||||
req.setTls(this.tls?.value ?? false);
|
||||
req.setUser(this.user?.value ?? '');
|
||||
|
||||
@ -152,6 +154,7 @@ export class NotificationSettingsComponent implements OnInit {
|
||||
req.setHost(this.hostAndPort?.value ?? '');
|
||||
req.setSenderAddress(this.senderAddress?.value ?? '');
|
||||
req.setSenderName(this.senderName?.value ?? '');
|
||||
req.setReplyToAddress(this.replyToAddress?.value ?? '');
|
||||
req.setTls(this.tls?.value ?? false);
|
||||
req.setUser(this.user?.value ?? '');
|
||||
|
||||
@ -298,6 +301,10 @@ export class NotificationSettingsComponent implements OnInit {
|
||||
return this.form.get('senderName');
|
||||
}
|
||||
|
||||
public get replyToAddress(): AbstractControl | null {
|
||||
return this.form.get('replyToAddress');
|
||||
}
|
||||
|
||||
public get tls(): AbstractControl | null {
|
||||
return this.form.get('tls');
|
||||
}
|
||||
|
@ -1049,6 +1049,7 @@
|
||||
"TITLE": "SMTP настройки",
|
||||
"SENDERADDRESS": "Имейл адрес на изпращача",
|
||||
"SENDERNAME": "Име на изпращача",
|
||||
"REPLYTOADDRESS": "Reply-to адрес",
|
||||
"HOSTANDPORT": "Хост и порт",
|
||||
"USER": "Потребител",
|
||||
"PASSWORD": "Парола",
|
||||
|
@ -1055,6 +1055,7 @@
|
||||
"TITLE": "SMTP Einstellungen",
|
||||
"SENDERADDRESS": "Sender Email-Adresse",
|
||||
"SENDERNAME": "Sender Name",
|
||||
"REPLYTOADDRESS": "Reply-to-Adresse",
|
||||
"HOSTANDPORT": "Host und Port",
|
||||
"USER": "Benutzer",
|
||||
"PASSWORD": "Passwort",
|
||||
|
@ -1056,6 +1056,7 @@
|
||||
"TITLE": "SMTP Settings",
|
||||
"SENDERADDRESS": "Sender Email Address",
|
||||
"SENDERNAME": "Sender Name",
|
||||
"REPLYTOADDRESS": "Reply-to Address",
|
||||
"HOSTANDPORT": "Host And Port",
|
||||
"USER": "User",
|
||||
"PASSWORD": "Password",
|
||||
|
@ -1056,6 +1056,7 @@
|
||||
"TITLE": "Ajustes SMTP",
|
||||
"SENDERADDRESS": "Dirección email del emisor",
|
||||
"SENDERNAME": "Nombre del emisor",
|
||||
"REPLYTOADDRESS": "Dirección Reply-To",
|
||||
"HOSTANDPORT": "Servidor y puerto",
|
||||
"USER": "Usuario",
|
||||
"PASSWORD": "Contraseña",
|
||||
|
@ -1055,6 +1055,7 @@
|
||||
"TITLE": "Paramètres SMTP",
|
||||
"SENDERADDRESS": "Adresse e-mail de l'expéditeur",
|
||||
"SENDERNAME": "Nom de l'expéditeur",
|
||||
"REPLYTOADDRESS": "Adresse Reply-to",
|
||||
"HOSTANDPORT": "Hôte et port",
|
||||
"USER": "Utilisateur",
|
||||
"PASSWORD": "Mot de passe",
|
||||
|
@ -1055,6 +1055,7 @@
|
||||
"TITLE": "Impostazioni SMTP",
|
||||
"SENDERADDRESS": "Indirizzo email del mittente",
|
||||
"SENDERNAME": "Nome del mittente",
|
||||
"REPLYTOADDRESS": "Indirizzo Reply-to",
|
||||
"HOSTANDPORT": "Host e porta",
|
||||
"USER": "Utente",
|
||||
"PASSWORD": "Password",
|
||||
|
@ -1056,6 +1056,7 @@
|
||||
"TITLE": "SMTP設定",
|
||||
"SENDERADDRESS": "送信者のメールアドレス",
|
||||
"SENDERNAME": "送信者名",
|
||||
"REPLYTOADDRESS": "返信先アドレス",
|
||||
"HOSTANDPORT": "ホストとポート",
|
||||
"USER": "ユーザー",
|
||||
"PASSWORD": "パスワード",
|
||||
|
@ -1056,6 +1056,7 @@
|
||||
"TITLE": "SMTP подесувања",
|
||||
"SENDERADDRESS": "Адреса на испраќачот",
|
||||
"SENDERNAME": "Име на испраќачот",
|
||||
"REPLYTOADDRESS": "Reply-to адреса",
|
||||
"HOSTANDPORT": "Host и Port",
|
||||
"USER": "Корисник",
|
||||
"PASSWORD": "Лозинка",
|
||||
|
@ -1055,6 +1055,7 @@
|
||||
"TITLE": "Ustawienia SMTP",
|
||||
"SENDERADDRESS": "Adres e-mail nadawcy",
|
||||
"SENDERNAME": "Nazwa nadawcy",
|
||||
"REPLYTOADDRESS": "Adres Reply-to",
|
||||
"HOSTANDPORT": "Host i port",
|
||||
"USER": "Użytkownik",
|
||||
"PASSWORD": "Hasło",
|
||||
|
@ -1056,6 +1056,7 @@
|
||||
"TITLE": "Configurações SMTP",
|
||||
"SENDERADDRESS": "Endereço de e-mail do remetente",
|
||||
"SENDERNAME": "Nome do remetente",
|
||||
"REPLYTOADDRESS": "Endereço Reply-To",
|
||||
"HOSTANDPORT": "Host e porta",
|
||||
"USER": "Usuário",
|
||||
"PASSWORD": "Senha",
|
||||
|
@ -1055,6 +1055,7 @@
|
||||
"TITLE": "SMTP 设置",
|
||||
"SENDERADDRESS": "发件人地址",
|
||||
"SENDERNAME": "发件人名称",
|
||||
"REPLYTOADDRESS": "Reply-to 地址",
|
||||
"HOSTANDPORT": "主机和端口",
|
||||
"USER": "用户名",
|
||||
"PASSWORD": "密码",
|
||||
|
@ -200,6 +200,7 @@ DefaultInstance:
|
||||
# if the host of the sender is different from ExternalDomain set DefaultInstance.DomainPolicy.SMTPSenderAddressMatchesInstanceDomain to false
|
||||
From:
|
||||
FromName:
|
||||
ReplyToAddress:
|
||||
```
|
||||
|
||||
- If you don't want to use the DefaultInstance configuration for the first instance that ZITADEL automatically creates for you during the [setup phase](/self-hosting/manage/configure#database-initialization), you can provide a FirstInstance YAML section using the --steps argument.
|
||||
|
@ -135,6 +135,7 @@ func AddSMTPToConfig(req *admin_pb.AddSMTPConfigRequest) *smtp.Config {
|
||||
Tls: req.Tls,
|
||||
From: req.SenderAddress,
|
||||
FromName: req.SenderName,
|
||||
ReplyToAddress: req.ReplyToAddress,
|
||||
SMTP: smtp.SMTP{
|
||||
Host: req.Host,
|
||||
User: req.User,
|
||||
@ -148,6 +149,7 @@ func UpdateSMTPToConfig(req *admin_pb.UpdateSMTPConfigRequest) *smtp.Config {
|
||||
Tls: req.Tls,
|
||||
From: req.SenderAddress,
|
||||
FromName: req.SenderName,
|
||||
ReplyToAddress: req.ReplyToAddress,
|
||||
SMTP: smtp.SMTP{
|
||||
Host: req.Host,
|
||||
User: req.User,
|
||||
@ -160,6 +162,7 @@ func SMTPConfigToPb(smtp *query.SMTPConfig) *settings_pb.SMTPConfig {
|
||||
Tls: smtp.TLS,
|
||||
SenderAddress: smtp.SenderAddress,
|
||||
SenderName: smtp.SenderName,
|
||||
ReplyToAddress: smtp.ReplyToAddress,
|
||||
Host: smtp.Host,
|
||||
User: smtp.User,
|
||||
Details: obj_grpc.ToViewDetailsPb(smtp.Sequence, smtp.CreationDate, smtp.ChangeDate, smtp.AggregateID),
|
||||
|
@ -414,6 +414,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str
|
||||
instanceAgg,
|
||||
setup.SMTPConfiguration.From,
|
||||
setup.SMTPConfiguration.FromName,
|
||||
setup.SMTPConfiguration.ReplyToAddress,
|
||||
setup.SMTPConfiguration.SMTP.Host,
|
||||
setup.SMTPConfiguration.SMTP.User,
|
||||
[]byte(setup.SMTPConfiguration.SMTP.Password),
|
||||
|
@ -14,6 +14,7 @@ type InstanceSMTPConfigWriteModel struct {
|
||||
|
||||
SenderAddress string
|
||||
SenderName string
|
||||
ReplyToAddress string
|
||||
TLS bool
|
||||
Host string
|
||||
User string
|
||||
@ -62,6 +63,7 @@ func (wm *InstanceSMTPConfigWriteModel) Reduce() error {
|
||||
wm.TLS = e.TLS
|
||||
wm.SenderAddress = e.SenderAddress
|
||||
wm.SenderName = e.SenderName
|
||||
wm.ReplyToAddress = e.ReplyToAddress
|
||||
wm.Host = e.Host
|
||||
wm.User = e.User
|
||||
wm.Password = e.Password
|
||||
@ -76,6 +78,9 @@ func (wm *InstanceSMTPConfigWriteModel) Reduce() error {
|
||||
if e.FromName != nil {
|
||||
wm.SenderName = *e.FromName
|
||||
}
|
||||
if e.ReplyToAddress != nil {
|
||||
wm.ReplyToAddress = *e.ReplyToAddress
|
||||
}
|
||||
if e.Host != nil {
|
||||
wm.Host = *e.Host
|
||||
}
|
||||
@ -87,6 +92,7 @@ func (wm *InstanceSMTPConfigWriteModel) Reduce() error {
|
||||
wm.TLS = false
|
||||
wm.SenderName = ""
|
||||
wm.SenderAddress = ""
|
||||
wm.ReplyToAddress = ""
|
||||
wm.Host = ""
|
||||
wm.User = ""
|
||||
wm.Password = nil
|
||||
@ -122,7 +128,7 @@ func (wm *InstanceSMTPConfigWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (wm *InstanceSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, tls bool, fromAddress, fromName, smtpHost, smtpUser string) (*instance.SMTPConfigChangedEvent, bool, error) {
|
||||
func (wm *InstanceSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, tls bool, fromAddress, fromName, replyToAddress, smtpHost, smtpUser string) (*instance.SMTPConfigChangedEvent, bool, error) {
|
||||
changes := make([]instance.SMTPConfigChanges, 0)
|
||||
var err error
|
||||
|
||||
@ -135,6 +141,9 @@ func (wm *InstanceSMTPConfigWriteModel) NewChangedEvent(ctx context.Context, agg
|
||||
if wm.SenderName != fromName {
|
||||
changes = append(changes, instance.ChangeSMTPConfigFromName(fromName))
|
||||
}
|
||||
if wm.ReplyToAddress != replyToAddress {
|
||||
changes = append(changes, instance.ChangeSMTPConfigReplyToAddress(replyToAddress))
|
||||
}
|
||||
if wm.Host != smtpHost {
|
||||
changes = append(changes, instance.ChangeSMTPConfigSMTPHost(smtpHost))
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
|
||||
func (c *Commands) AddSMTPConfig(ctx context.Context, config *smtp.Config) (*domain.ObjectDetails, error) {
|
||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||
validation := c.prepareAddSMTPConfig(instanceAgg, config.From, config.FromName, config.SMTP.Host, config.SMTP.User, []byte(config.SMTP.Password), config.Tls)
|
||||
validation := c.prepareAddSMTPConfig(instanceAgg, config.From, config.FromName, config.ReplyToAddress, config.SMTP.Host, config.SMTP.User, []byte(config.SMTP.Password), config.Tls)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -35,7 +35,7 @@ func (c *Commands) AddSMTPConfig(ctx context.Context, config *smtp.Config) (*dom
|
||||
|
||||
func (c *Commands) ChangeSMTPConfig(ctx context.Context, config *smtp.Config) (*domain.ObjectDetails, error) {
|
||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||
validation := c.prepareChangeSMTPConfig(instanceAgg, config.From, config.FromName, config.SMTP.Host, config.SMTP.User, config.Tls)
|
||||
validation := c.prepareChangeSMTPConfig(instanceAgg, config.From, config.FromName, config.ReplyToAddress, config.SMTP.Host, config.SMTP.User, config.Tls)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -99,11 +99,14 @@ func (c *Commands) RemoveSMTPConfig(ctx context.Context) (*domain.ObjectDetails,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, hostAndPort, user string, password []byte, tls bool) preparation.Validation {
|
||||
func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, replyTo, hostAndPort, user string, password []byte, tls bool) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if from = strings.TrimSpace(from); from == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "INST-mruNY", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
replyTo = strings.TrimSpace(replyTo)
|
||||
|
||||
hostAndPort = strings.TrimSpace(hostAndPort)
|
||||
if _, _, err := net.SplitHostPort(hostAndPort); err != nil {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "INST-9JdRe", "Errors.Invalid.Argument")
|
||||
@ -136,6 +139,7 @@ func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, hostA
|
||||
tls,
|
||||
from,
|
||||
name,
|
||||
replyTo,
|
||||
hostAndPort,
|
||||
user,
|
||||
smtpPassword,
|
||||
@ -145,11 +149,13 @@ func (c *Commands) prepareAddSMTPConfig(a *instance.Aggregate, from, name, hostA
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, hostAndPort, user string, tls bool) preparation.Validation {
|
||||
func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, replyTo, hostAndPort, user string, tls bool) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
if from = strings.TrimSpace(from); from == "" {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "INST-ASv2d", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
replyTo = strings.TrimSpace(replyTo)
|
||||
hostAndPort = strings.TrimSpace(hostAndPort)
|
||||
if _, _, err := net.SplitHostPort(hostAndPort); err != nil {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "INST-Kv875", "Errors.Invalid.Argument")
|
||||
@ -174,6 +180,7 @@ func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, ho
|
||||
tls,
|
||||
from,
|
||||
name,
|
||||
replyTo,
|
||||
hostAndPort,
|
||||
user,
|
||||
)
|
||||
|
@ -95,6 +95,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
|
||||
true,
|
||||
"from@domain.ch",
|
||||
"name",
|
||||
"",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{},
|
||||
@ -109,6 +110,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
|
||||
Tls: true,
|
||||
From: "from@domain.ch",
|
||||
FromName: "name",
|
||||
ReplyToAddress: "",
|
||||
SMTP: smtp.SMTP{
|
||||
Host: "host:587",
|
||||
User: "user",
|
||||
@ -150,6 +152,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
|
||||
true,
|
||||
"from@domain.ch",
|
||||
"name",
|
||||
"",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{
|
||||
@ -184,6 +187,72 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "add smtp config with reply to address, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
instance.NewDomainAddedEvent(context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
"domain.ch",
|
||||
false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
instance.NewDomainPolicyAddedEvent(context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
true, true, false,
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusherWithInstanceID(
|
||||
"INSTANCE",
|
||||
instance.NewSMTPConfigAddedEvent(
|
||||
context.Background(),
|
||||
&instance.NewAggregate("INSTANCE").Aggregate,
|
||||
true,
|
||||
"from@domain.ch",
|
||||
"name",
|
||||
"replyto@domain.ch",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("password"),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
alg: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
|
||||
smtp: &smtp.Config{
|
||||
Tls: true,
|
||||
From: "from@domain.ch",
|
||||
FromName: "name",
|
||||
ReplyToAddress: "replyto@domain.ch",
|
||||
SMTP: smtp.SMTP{
|
||||
Host: "host:587",
|
||||
User: "user",
|
||||
Password: "password",
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.ObjectDetails{
|
||||
ResourceOwner: "INSTANCE",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "smtp config, port is missing",
|
||||
fields: fields{
|
||||
@ -258,6 +327,7 @@ func TestCommandSide_AddSMTPConfig(t *testing.T) {
|
||||
true,
|
||||
"from@domain.ch",
|
||||
"name",
|
||||
"",
|
||||
"[2001:db8::1]:2525",
|
||||
"user",
|
||||
&crypto.CryptoValue{
|
||||
@ -396,6 +466,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
|
||||
true,
|
||||
"from@domain.ch",
|
||||
"name",
|
||||
"",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{},
|
||||
@ -446,6 +517,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
|
||||
true,
|
||||
"from@domain.ch",
|
||||
"name",
|
||||
"",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{},
|
||||
@ -496,6 +568,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
|
||||
true,
|
||||
"from@domain.ch",
|
||||
"name",
|
||||
"",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{},
|
||||
@ -511,6 +584,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
|
||||
false,
|
||||
"from2@domain.ch",
|
||||
"name2",
|
||||
"replyto@domain.ch",
|
||||
"host2:587",
|
||||
"user2",
|
||||
),
|
||||
@ -525,6 +599,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
|
||||
Tls: false,
|
||||
From: "from2@domain.ch",
|
||||
FromName: "name2",
|
||||
ReplyToAddress: "replyto@domain.ch",
|
||||
SMTP: smtp.SMTP{
|
||||
Host: "host2:587",
|
||||
User: "user2",
|
||||
@ -607,6 +682,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
|
||||
true,
|
||||
"from@domain.ch",
|
||||
"name",
|
||||
"",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{},
|
||||
@ -622,6 +698,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
|
||||
false,
|
||||
"from2@domain.ch",
|
||||
"name2",
|
||||
"replyto@domain.ch",
|
||||
"[2001:db8::1]:2525",
|
||||
"user2",
|
||||
),
|
||||
@ -636,6 +713,7 @@ func TestCommandSide_ChangeSMTPConfig(t *testing.T) {
|
||||
Tls: false,
|
||||
From: "from2@domain.ch",
|
||||
FromName: "name2",
|
||||
ReplyToAddress: "replyto@domain.ch",
|
||||
SMTP: smtp.SMTP{
|
||||
Host: "[2001:db8::1]:2525",
|
||||
User: "user2",
|
||||
@ -716,6 +794,7 @@ func TestCommandSide_ChangeSMTPConfigPassword(t *testing.T) {
|
||||
true,
|
||||
"from",
|
||||
"name",
|
||||
"",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{},
|
||||
@ -819,6 +898,7 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) {
|
||||
true,
|
||||
"from",
|
||||
"name",
|
||||
"",
|
||||
"host:587",
|
||||
"user",
|
||||
&crypto.CryptoValue{},
|
||||
@ -868,11 +948,12 @@ func TestCommandSide_RemoveSMTPConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func newSMTPConfigChangedEvent(ctx context.Context, tls bool, fromAddress, fromName, host, user string) *instance.SMTPConfigChangedEvent {
|
||||
func newSMTPConfigChangedEvent(ctx context.Context, tls bool, fromAddress, fromName, replyTo, host, user string) *instance.SMTPConfigChangedEvent {
|
||||
changes := []instance.SMTPConfigChanges{
|
||||
instance.ChangeSMTPConfigTLS(tls),
|
||||
instance.ChangeSMTPConfigFromAddress(fromAddress),
|
||||
instance.ChangeSMTPConfigFromName(fromName),
|
||||
instance.ChangeSMTPConfigReplyToAddress(replyTo),
|
||||
instance.ChangeSMTPConfigSMTPHost(host),
|
||||
instance.ChangeSMTPConfigSMTPUser(user),
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ type Email struct {
|
||||
smtpClient *smtp.Client
|
||||
senderAddress string
|
||||
senderName string
|
||||
replyToAddress string
|
||||
}
|
||||
|
||||
func InitChannel(ctx context.Context, getSMTPConfig func(ctx context.Context) (*Config, error)) (*Email, error) {
|
||||
@ -39,6 +40,7 @@ func InitChannel(ctx context.Context, getSMTPConfig func(ctx context.Context) (*
|
||||
smtpClient: client,
|
||||
senderName: smtpConfig.FromName,
|
||||
senderAddress: smtpConfig.From,
|
||||
replyToAddress: smtpConfig.ReplyToAddress,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -54,6 +56,7 @@ func (email *Email) HandleMessage(message channels.Message) error {
|
||||
}
|
||||
emailMsg.SenderEmail = email.senderAddress
|
||||
emailMsg.SenderName = email.senderName
|
||||
emailMsg.ReplyToAddress = email.replyToAddress
|
||||
// To && From
|
||||
if err := email.smtpClient.Mail(emailMsg.SenderEmail); err != nil {
|
||||
return caos_errs.ThrowInternalf(err, "EMAIL-s3is3", "could not set sender: %v", emailMsg.SenderEmail)
|
||||
|
@ -5,6 +5,7 @@ type Config struct {
|
||||
Tls bool
|
||||
From string
|
||||
FromName string
|
||||
ReplyToAddress string
|
||||
}
|
||||
|
||||
type SMTP struct {
|
||||
|
@ -21,6 +21,7 @@ func (n *NotificationQueries) GetSMTPConfig(ctx context.Context) (*smtp.Config,
|
||||
return &smtp.Config{
|
||||
From: config.SenderAddress,
|
||||
FromName: config.SenderName,
|
||||
ReplyToAddress: config.ReplyToAddress,
|
||||
Tls: config.TLS,
|
||||
SMTP: smtp.SMTP{
|
||||
Host: config.Host,
|
||||
|
@ -23,6 +23,7 @@ type Email struct {
|
||||
CC []string
|
||||
SenderEmail string
|
||||
SenderName string
|
||||
ReplyToAddress string
|
||||
Subject string
|
||||
Content string
|
||||
TriggeringEvent eventstore.Event
|
||||
@ -35,6 +36,9 @@ func (msg *Email) GetContent() (string, error) {
|
||||
from = fmt.Sprintf("%s <%s>", msg.SenderName, msg.SenderEmail)
|
||||
}
|
||||
headers["From"] = from
|
||||
if msg.ReplyToAddress != "" {
|
||||
headers["Reply-to"] = msg.ReplyToAddress
|
||||
}
|
||||
headers["Return-Path"] = msg.SenderEmail
|
||||
headers["To"] = strings.Join(msg.Recipients, ", ")
|
||||
headers["Cc"] = strings.Join(msg.CC, ", ")
|
||||
|
@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
SMTPConfigProjectionTable = "projections.smtp_configs"
|
||||
SMTPConfigProjectionTable = "projections.smtp_configs1"
|
||||
|
||||
SMTPConfigColumnAggregateID = "aggregate_id"
|
||||
SMTPConfigColumnCreationDate = "creation_date"
|
||||
@ -22,6 +22,7 @@ const (
|
||||
SMTPConfigColumnTLS = "tls"
|
||||
SMTPConfigColumnSenderAddress = "sender_address"
|
||||
SMTPConfigColumnSenderName = "sender_name"
|
||||
SMTPConfigColumnReplyToAddress = "reply_to_address"
|
||||
SMTPConfigColumnSMTPHost = "host"
|
||||
SMTPConfigColumnSMTPUser = "username"
|
||||
SMTPConfigColumnSMTPPassword = "password"
|
||||
@ -46,6 +47,7 @@ func newSMTPConfigProjection(ctx context.Context, config crdb.StatementHandlerCo
|
||||
crdb.NewColumn(SMTPConfigColumnTLS, crdb.ColumnTypeBool),
|
||||
crdb.NewColumn(SMTPConfigColumnSenderAddress, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(SMTPConfigColumnSenderName, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(SMTPConfigColumnReplyToAddress, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(SMTPConfigColumnSMTPHost, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(SMTPConfigColumnSMTPUser, crdb.ColumnTypeText),
|
||||
crdb.NewColumn(SMTPConfigColumnSMTPPassword, crdb.ColumnTypeJSONB, crdb.Nullable()),
|
||||
@ -104,6 +106,7 @@ func (p *smtpConfigProjection) reduceSMTPConfigAdded(event eventstore.Event) (*h
|
||||
handler.NewCol(SMTPConfigColumnTLS, e.TLS),
|
||||
handler.NewCol(SMTPConfigColumnSenderAddress, e.SenderAddress),
|
||||
handler.NewCol(SMTPConfigColumnSenderName, e.SenderName),
|
||||
handler.NewCol(SMTPConfigColumnReplyToAddress, e.ReplyToAddress),
|
||||
handler.NewCol(SMTPConfigColumnSMTPHost, e.Host),
|
||||
handler.NewCol(SMTPConfigColumnSMTPUser, e.User),
|
||||
handler.NewCol(SMTPConfigColumnSMTPPassword, e.Password),
|
||||
@ -117,7 +120,7 @@ func (p *smtpConfigProjection) reduceSMTPConfigChanged(event eventstore.Event) (
|
||||
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-wl0wd", "reduce.wrong.event.type %s", instance.SMTPConfigChangedEventType)
|
||||
}
|
||||
|
||||
columns := make([]handler.Column, 0, 7)
|
||||
columns := make([]handler.Column, 0, 8)
|
||||
columns = append(columns, handler.NewCol(SMTPConfigColumnChangeDate, e.CreationDate()),
|
||||
handler.NewCol(SMTPConfigColumnSequence, e.Sequence()))
|
||||
if e.TLS != nil {
|
||||
@ -129,6 +132,9 @@ func (p *smtpConfigProjection) reduceSMTPConfigChanged(event eventstore.Event) (
|
||||
if e.FromName != nil {
|
||||
columns = append(columns, handler.NewCol(SMTPConfigColumnSenderName, *e.FromName))
|
||||
}
|
||||
if e.ReplyToAddress != nil {
|
||||
columns = append(columns, handler.NewCol(SMTPConfigColumnReplyToAddress, *e.ReplyToAddress))
|
||||
}
|
||||
if e.Host != nil {
|
||||
columns = append(columns, handler.NewCol(SMTPConfigColumnSMTPHost, *e.Host))
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
"tls": true,
|
||||
"senderAddress": "sender",
|
||||
"senderName": "name",
|
||||
"replyToAddress": "reply-to",
|
||||
"host": "host",
|
||||
"user": "user"
|
||||
}`,
|
||||
@ -44,13 +45,14 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs SET (change_date, sequence, tls, sender_address, sender_name, host, username) = ($1, $2, $3, $4, $5, $6, $7) WHERE (aggregate_id = $8) AND (instance_id = $9)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs1 SET (change_date, sequence, tls, sender_address, sender_name, reply_to_address, host, username) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (aggregate_id = $9) AND (instance_id = $10)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
true,
|
||||
"sender",
|
||||
"name",
|
||||
"reply-to",
|
||||
"host",
|
||||
"user",
|
||||
"agg-id",
|
||||
@ -71,6 +73,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
"tls": true,
|
||||
"senderAddress": "sender",
|
||||
"senderName": "name",
|
||||
"replyToAddress": "reply-to",
|
||||
"host": "host",
|
||||
"user": "user",
|
||||
"password": {
|
||||
@ -89,7 +92,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.smtp_configs (aggregate_id, creation_date, change_date, resource_owner, instance_id, sequence, tls, sender_address, sender_name, host, username, password) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)",
|
||||
expectedStmt: "INSERT INTO projections.smtp_configs1 (aggregate_id, creation_date, change_date, resource_owner, instance_id, sequence, tls, sender_address, sender_name, reply_to_address, host, username, password) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
anyArg{},
|
||||
@ -100,6 +103,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
true,
|
||||
"sender",
|
||||
"name",
|
||||
"reply-to",
|
||||
"host",
|
||||
"user",
|
||||
anyArg{},
|
||||
@ -132,7 +136,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "UPDATE projections.smtp_configs SET (change_date, sequence, password) = ($1, $2, $3) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedStmt: "UPDATE projections.smtp_configs1 SET (change_date, sequence, password) = ($1, $2, $3) WHERE (aggregate_id = $4) AND (instance_id = $5)",
|
||||
expectedArgs: []interface{}{
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
@ -162,7 +166,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.smtp_configs WHERE (aggregate_id = $1) AND (instance_id = $2)",
|
||||
expectedStmt: "DELETE FROM projections.smtp_configs1 WHERE (aggregate_id = $1) AND (instance_id = $2)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
"instance-id",
|
||||
@ -189,7 +193,7 @@ func TestSMTPConfigProjection_reduces(t *testing.T) {
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: "DELETE FROM projections.smtp_configs WHERE (instance_id = $1)",
|
||||
expectedStmt: "DELETE FROM projections.smtp_configs1 WHERE (instance_id = $1)",
|
||||
expectedArgs: []interface{}{
|
||||
"agg-id",
|
||||
},
|
||||
|
@ -57,6 +57,10 @@ var (
|
||||
name: projection.SMTPConfigColumnSenderName,
|
||||
table: smtpConfigsTable,
|
||||
}
|
||||
SMTPConfigColumnReplyToAddress = Column{
|
||||
name: projection.SMTPConfigColumnReplyToAddress,
|
||||
table: smtpConfigsTable,
|
||||
}
|
||||
SMTPConfigColumnSMTPHost = Column{
|
||||
name: projection.SMTPConfigColumnSMTPHost,
|
||||
table: smtpConfigsTable,
|
||||
@ -86,6 +90,7 @@ type SMTPConfig struct {
|
||||
TLS bool
|
||||
SenderAddress string
|
||||
SenderName string
|
||||
ReplyToAddress string
|
||||
Host string
|
||||
User string
|
||||
Password *crypto.CryptoValue
|
||||
@ -123,6 +128,7 @@ func prepareSMTPConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
|
||||
SMTPConfigColumnTLS.identifier(),
|
||||
SMTPConfigColumnSenderAddress.identifier(),
|
||||
SMTPConfigColumnSenderName.identifier(),
|
||||
SMTPConfigColumnReplyToAddress.identifier(),
|
||||
SMTPConfigColumnSMTPHost.identifier(),
|
||||
SMTPConfigColumnSMTPUser.identifier(),
|
||||
SMTPConfigColumnSMTPPassword.identifier()).
|
||||
@ -139,6 +145,7 @@ func prepareSMTPConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
|
||||
&config.TLS,
|
||||
&config.SenderAddress,
|
||||
&config.SenderName,
|
||||
&config.ReplyToAddress,
|
||||
&config.Host,
|
||||
&config.User,
|
||||
&password,
|
||||
|
@ -13,18 +13,19 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
prepareSMTPConfigStmt = `SELECT projections.smtp_configs.aggregate_id,` +
|
||||
` projections.smtp_configs.creation_date,` +
|
||||
` projections.smtp_configs.change_date,` +
|
||||
` projections.smtp_configs.resource_owner,` +
|
||||
` projections.smtp_configs.sequence,` +
|
||||
` projections.smtp_configs.tls,` +
|
||||
` projections.smtp_configs.sender_address,` +
|
||||
` projections.smtp_configs.sender_name,` +
|
||||
` projections.smtp_configs.host,` +
|
||||
` projections.smtp_configs.username,` +
|
||||
` projections.smtp_configs.password` +
|
||||
` FROM projections.smtp_configs` +
|
||||
prepareSMTPConfigStmt = `SELECT projections.smtp_configs1.aggregate_id,` +
|
||||
` projections.smtp_configs1.creation_date,` +
|
||||
` projections.smtp_configs1.change_date,` +
|
||||
` projections.smtp_configs1.resource_owner,` +
|
||||
` projections.smtp_configs1.sequence,` +
|
||||
` projections.smtp_configs1.tls,` +
|
||||
` projections.smtp_configs1.sender_address,` +
|
||||
` projections.smtp_configs1.sender_name,` +
|
||||
` projections.smtp_configs1.reply_to_address,` +
|
||||
` projections.smtp_configs1.host,` +
|
||||
` projections.smtp_configs1.username,` +
|
||||
` projections.smtp_configs1.password` +
|
||||
` FROM projections.smtp_configs1` +
|
||||
` AS OF SYSTEM TIME '-1 ms'`
|
||||
prepareSMTPConfigCols = []string{
|
||||
"aggregate_id",
|
||||
@ -35,6 +36,7 @@ var (
|
||||
"tls",
|
||||
"sender_address",
|
||||
"sender_name",
|
||||
"reply_to_address",
|
||||
"smtp_host",
|
||||
"smtp_user",
|
||||
"smtp_password",
|
||||
@ -86,6 +88,7 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
|
||||
true,
|
||||
"sender",
|
||||
"name",
|
||||
"reply-to",
|
||||
"host",
|
||||
"user",
|
||||
&crypto.CryptoValue{},
|
||||
@ -101,6 +104,7 @@ func Test_SMTPConfigsPrepares(t *testing.T) {
|
||||
TLS: true,
|
||||
SenderAddress: "sender",
|
||||
SenderName: "name",
|
||||
ReplyToAddress: "reply-to",
|
||||
Host: "host",
|
||||
User: "user",
|
||||
Password: &crypto.CryptoValue{},
|
||||
|
@ -23,6 +23,7 @@ type SMTPConfigAddedEvent struct {
|
||||
|
||||
SenderAddress string `json:"senderAddress,omitempty"`
|
||||
SenderName string `json:"senderName,omitempty"`
|
||||
ReplyToAddress string `json:"replyToAddress,omitempty"`
|
||||
TLS bool `json:"tls,omitempty"`
|
||||
Host string `json:"host,omitempty"`
|
||||
User string `json:"user,omitempty"`
|
||||
@ -35,6 +36,7 @@ func NewSMTPConfigAddedEvent(
|
||||
tls bool,
|
||||
senderAddress,
|
||||
senderName,
|
||||
replyToAddress,
|
||||
host,
|
||||
user string,
|
||||
password *crypto.CryptoValue,
|
||||
@ -48,6 +50,7 @@ func NewSMTPConfigAddedEvent(
|
||||
TLS: tls,
|
||||
SenderAddress: senderAddress,
|
||||
SenderName: senderName,
|
||||
ReplyToAddress: replyToAddress,
|
||||
Host: host,
|
||||
User: user,
|
||||
Password: password,
|
||||
@ -79,6 +82,7 @@ type SMTPConfigChangedEvent struct {
|
||||
|
||||
FromAddress *string `json:"senderAddress,omitempty"`
|
||||
FromName *string `json:"senderName,omitempty"`
|
||||
ReplyToAddress *string `json:"replyToAddress,omitempty"`
|
||||
TLS *bool `json:"tls,omitempty"`
|
||||
Host *string `json:"host,omitempty"`
|
||||
User *string `json:"user,omitempty"`
|
||||
@ -133,6 +137,12 @@ func ChangeSMTPConfigFromName(senderName string) func(event *SMTPConfigChangedEv
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeSMTPConfigReplyToAddress(replyToAddress string) func(event *SMTPConfigChangedEvent) {
|
||||
return func(e *SMTPConfigChangedEvent) {
|
||||
e.ReplyToAddress = &replyToAddress
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeSMTPConfigSMTPHost(smtpHost string) func(event *SMTPConfigChangedEvent) {
|
||||
return func(e *SMTPConfigChangedEvent) {
|
||||
e.Host = &smtpHost
|
||||
|
@ -3842,6 +3842,14 @@ message AddSMTPConfigRequest {
|
||||
example: "\"this-is-my-password\"";
|
||||
}
|
||||
];
|
||||
string reply_to_address = 7 [
|
||||
(validate.rules).string = {min_len: 0, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"replyto@m.zitadel.cloud\"";
|
||||
min_length: 1;
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message AddSMTPConfigResponse {
|
||||
@ -3883,6 +3891,14 @@ message UpdateSMTPConfigRequest {
|
||||
example: "\"197f0117-529e-443d-bf6c-0292dd9a02b7\"";
|
||||
}
|
||||
];
|
||||
string reply_to_address = 6 [
|
||||
(validate.rules).string = {min_len: 0, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"replyto@m.zitadel.cloud\"";
|
||||
min_length: 1;
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message UpdateSMTPConfigResponse {
|
||||
|
@ -75,6 +75,11 @@ message SMTPConfig {
|
||||
example: "\"197f0117-529e-443d-bf6c-0292dd9a02b7\"";
|
||||
}
|
||||
];
|
||||
string reply_to_address = 7 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"replyto@m.zitadel.cloud\"";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message SMSProvider {
|
||||
|
Loading…
x
Reference in New Issue
Block a user