Skip to content

Commit 72c62c8

Browse files
FEATURE (email): Allow SMTP without auth
1 parent 8938cd1 commit 72c62c8

File tree

4 files changed

+47
-28
lines changed

4 files changed

+47
-28
lines changed

backend/internal/features/notifiers/models/email_notifier/model.go

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ type EmailNotifier struct {
2525
TargetEmail string `json:"targetEmail" gorm:"not null;type:varchar(255);column:target_email"`
2626
SMTPHost string `json:"smtpHost" gorm:"not null;type:varchar(255);column:smtp_host"`
2727
SMTPPort int `json:"smtpPort" gorm:"not null;column:smtp_port"`
28-
SMTPUser string `json:"smtpUser" gorm:"not null;type:varchar(255);column:smtp_user"`
29-
SMTPPassword string `json:"smtpPassword" gorm:"not null;type:varchar(255);column:smtp_password"`
28+
SMTPUser string `json:"smtpUser" gorm:"type:varchar(255);column:smtp_user"`
29+
SMTPPassword string `json:"smtpPassword" gorm:"type:varchar(255);column:smtp_password"`
3030
}
3131

3232
func (e *EmailNotifier) TableName() string {
@@ -46,12 +46,9 @@ func (e *EmailNotifier) Validate() error {
4646
return errors.New("SMTP port is required")
4747
}
4848

49-
if e.SMTPUser == "" {
50-
return errors.New("SMTP user is required")
51-
}
52-
53-
if e.SMTPPassword == "" {
54-
return errors.New("SMTP password is required")
49+
// Authentication is optional - both user and password must be provided together or both empty
50+
if (e.SMTPUser == "") != (e.SMTPPassword == "") {
51+
return errors.New("SMTP user and password must both be provided or both be empty")
5552
}
5653

5754
return nil
@@ -60,6 +57,10 @@ func (e *EmailNotifier) Validate() error {
6057
func (e *EmailNotifier) Send(logger *slog.Logger, heading string, message string) error {
6158
// Compose email
6259
from := e.SMTPUser
60+
if from == "" {
61+
from = "noreply@" + e.SMTPHost
62+
}
63+
6364
to := []string{e.TargetEmail}
6465

6566
// Format the email content
@@ -78,6 +79,9 @@ func (e *EmailNotifier) Send(logger *slog.Logger, heading string, message string
7879
addr := net.JoinHostPort(e.SMTPHost, fmt.Sprintf("%d", e.SMTPPort))
7980
timeout := DefaultTimeout
8081

82+
// Determine if authentication is required
83+
authRequired := e.SMTPUser != "" && e.SMTPPassword != ""
84+
8185
// Handle different port scenarios
8286
if e.SMTPPort == ImplicitTLSPort {
8387
// Implicit TLS (port 465)
@@ -105,10 +109,12 @@ func (e *EmailNotifier) Send(logger *slog.Logger, heading string, message string
105109
_ = client.Quit()
106110
}()
107111

108-
// Set up authentication
109-
auth := smtp.PlainAuth("", e.SMTPUser, e.SMTPPassword, e.SMTPHost)
110-
if err := client.Auth(auth); err != nil {
111-
return fmt.Errorf("SMTP authentication failed: %w", err)
112+
// Set up authentication only if credentials are provided
113+
if authRequired {
114+
auth := smtp.PlainAuth("", e.SMTPUser, e.SMTPPassword, e.SMTPHost)
115+
if err := client.Auth(auth); err != nil {
116+
return fmt.Errorf("SMTP authentication failed: %w", err)
117+
}
112118
}
113119

114120
// Set sender and recipients
@@ -138,9 +144,6 @@ func (e *EmailNotifier) Send(logger *slog.Logger, heading string, message string
138144
return nil
139145
} else {
140146
// STARTTLS (port 587) or other ports
141-
// Set up authentication information
142-
auth := smtp.PlainAuth("", e.SMTPUser, e.SMTPPassword, e.SMTPHost)
143-
144147
// Create a custom dialer with timeout
145148
dialer := &net.Dialer{Timeout: timeout}
146149
conn, err := dialer.Dial("tcp", addr)
@@ -169,8 +172,12 @@ func (e *EmailNotifier) Send(logger *slog.Logger, heading string, message string
169172
}
170173
}
171174

172-
if err := client.Auth(auth); err != nil {
173-
return fmt.Errorf("SMTP authentication failed: %w", err)
175+
// Authenticate only if credentials are provided
176+
if authRequired {
177+
auth := smtp.PlainAuth("", e.SMTPUser, e.SMTPPassword, e.SMTPHost)
178+
if err := client.Auth(auth); err != nil {
179+
return fmt.Errorf("SMTP authentication failed: %w", err)
180+
}
174181
}
175182

176183
if err := client.Mail(from); err != nil {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-- +goose Up
2+
-- +goose StatementBegin
3+
4+
ALTER TABLE email_notifiers
5+
ALTER COLUMN smtp_user DROP NOT NULL,
6+
ALTER COLUMN smtp_password DROP NOT NULL;
7+
8+
-- +goose StatementEnd
9+
10+
-- +goose Down
11+
-- +goose StatementBegin
12+
13+
ALTER TABLE email_notifiers
14+
ALTER COLUMN smtp_user SET NOT NULL,
15+
ALTER COLUMN smtp_password SET NOT NULL;
16+
17+
-- +goose StatementEnd

frontend/src/entity/notifiers/models/email/validateEmailNotifier.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,5 @@ export const validateEmailNotifier = (notifier: EmailNotifier): boolean => {
1313
return false;
1414
}
1515

16-
if (!notifier.smtpUser) {
17-
return false;
18-
}
19-
20-
if (!notifier.smtpPassword) {
21-
return false;
22-
}
23-
2416
return true;
2517
};

frontend/src/features/notifiers/ui/edit/EditNotifierComponent.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,12 @@ export function EditNotifierComponent({
7272
});
7373
} catch (e) {
7474
alert((e as Error).message);
75-
alert(
76-
'Make sure channel is public or bot is added to the private channel (via @invite) or group. For direct messages use User ID from Slack profile.',
77-
);
75+
76+
if (notifier.notifierType === NotifierType.SLACK) {
77+
alert(
78+
'Make sure channel is public or bot is added to the private channel (via @invite) or group. For direct messages use User ID from Slack profile.',
79+
);
80+
}
7881
}
7982

8083
setIsSendingTestNotification(false);

0 commit comments

Comments
 (0)