Compare commits

...

5 Commits

Author SHA1 Message Date
537fbeebd9 fix: customVerify cert alg + update valid CNs 2025-10-20 18:57:29 +00:00
34f9238cf0 fix; raw message no subject 2025-05-29 10:53:09 +02:00
e04ed76fee feat: SendRaw 2025-05-29 09:34:24 +02:00
b57eb95497 refactor: insecure no auth/tls 2025-05-29 09:24:26 +02:00
9844ebc65d refactor: remove file 2025-03-19 15:45:33 +01:00
4 changed files with 89 additions and 55 deletions

View File

@@ -9,14 +9,14 @@ import (
func main() { func main() {
// Here fill with real data // Here fill with real data
emailService := email.NewInsecure(email.MailServiceConfig{ emailService := email.NewInsecure(email.SecureConfig{
Auth: smtp.PlainAuth("", "your@email.com", "your-password", "smtp.youremail.com"), Auth: smtp.PlainAuth("", "your@email.com", "your-password", "smtp.youremail.com"),
Host: "smtp.youremail.com", Host: "smtp.youremail.com",
Port: "587", Port: "587",
From: "your@email.com", From: "your@email.com",
}) })
emailService.SendEmail(email.EmailMessage{ emailService.SendEmail(email.MessageWithAttachments{
To: "other@email.com", To: "other@email.com",
Subject: "Test Email", Subject: "Test Email",
Body: "<html><body><p>Here your body, you can attach as html<p/></body></html>", Body: "<html><body><p>Here your body, you can attach as html<p/></body></html>",

View File

@@ -18,6 +18,31 @@ const (
delimeter = "**=myohmy689407924327" delimeter = "**=myohmy689407924327"
) )
type InsecureConfig struct {
Host string
Port string
From string // Sender email address
}
type SecureConfig struct {
Auth smtp.Auth
Host string
Port string
From string // Sender email address
}
type MessageWithAttachments struct {
To string
Subject string
Body string
Attachments []EmailAttachment
}
type RawMessage struct {
To string
Body string
}
type SMTPClientIface interface { type SMTPClientIface interface {
StartTLS(*tls.Config) error StartTLS(*tls.Config) error
Auth(a smtp.Auth) error Auth(a smtp.Auth) error
@@ -39,7 +64,16 @@ type EmailService struct {
dial SmtpDialFn dial SmtpDialFn
} }
func NewInsecure(config MailServiceConfig) *EmailService { func NewInsecureNoAuth(config InsecureConfig) *EmailService {
return &EmailService{
host: config.Host,
port: config.Port,
from: config.From,
dial: dial,
}
}
func NewInsecure(config SecureConfig) *EmailService {
return &EmailService{ return &EmailService{
auth: config.Auth, auth: config.Auth,
host: config.Host, host: config.Host,
@@ -57,8 +91,10 @@ var validCommonNames = []string{
"ISRG Root X1", "ISRG Root X1",
"R3", "R3",
"R10", "R10",
"R13",
"R11", "R11",
"E5", "E5",
"E7",
"DST Root CA X3", "DST Root CA X3",
"DigiCert Global Root G2", "DigiCert Global Root G2",
"DigiCert Global G2 TLS RSA SHA256 2020 CA1", "DigiCert Global G2 TLS RSA SHA256 2020 CA1",
@@ -104,17 +140,22 @@ func customVerify(host string) func(cs tls.ConnectionState) error {
return fmt.Errorf("untrusted certificate issuer: %s", cert.Issuer.CommonName) return fmt.Errorf("untrusted certificate issuer: %s", cert.Issuer.CommonName)
} }
// Check that the public key algorithm is RSA. // Check that the public key algorithms
if cert.PublicKeyAlgorithm != x509.RSA { switch cert.PublicKeyAlgorithm {
return fmt.Errorf("unsupported public key algorithm: %v", cert.PublicKeyAlgorithm) case x509.RSA, x509.ECDSA:
// OK
default:
return fmt.Errorf("unsupported public key algorithm: %v",
cert.PublicKeyAlgorithm)
} }
} }
return nil return nil
} }
} }
func NewSecure(config MailServiceConfig) *EmailService { func NewSecure(config SecureConfig) *EmailService {
return &EmailService{ return &EmailService{
auth: config.Auth, auth: config.Auth,
host: config.Host, host: config.Host,
@@ -129,7 +170,7 @@ func NewSecure(config MailServiceConfig) *EmailService {
} }
} }
func NewSecure465(config MailServiceConfig) *EmailService { func NewSecure465(config SecureConfig) *EmailService {
tlsCfg := tls.Config{ tlsCfg := tls.Config{
// Ideally, InsecureSkipVerify: false, // Ideally, InsecureSkipVerify: false,
// or do a proper certificate validation // or do a proper certificate validation
@@ -149,13 +190,6 @@ func NewSecure465(config MailServiceConfig) *EmailService {
} }
} }
type MailServiceConfig struct {
Auth smtp.Auth
Host string
Port string
From string // Sender email address
}
func dial(hostPort string) (SMTPClientIface, error) { func dial(hostPort string) (SMTPClientIface, error) {
client, err := smtp.Dial(hostPort) client, err := smtp.Dial(hostPort)
if err != nil { if err != nil {
@@ -183,7 +217,7 @@ func dialTLS(hostPort string, tlsConfig *tls.Config) (SMTPClientIface, error) {
return c, nil return c, nil
} }
func (e *EmailService) SendEmail(emailData EmailMessage) error { func (e *EmailService) SendEmail(emailData MessageWithAttachments) error {
msg, err := newMessage(e.from, emailData.To, emailData.Subject). msg, err := newMessage(e.from, emailData.To, emailData.Subject).
withAttachments(emailData.Body, emailData.Attachments) withAttachments(emailData.Body, emailData.Attachments)
@@ -199,20 +233,33 @@ func (e *EmailService) SendEmail(emailData EmailMessage) error {
} }
} }
func (e *EmailService) SendRaw(emailData RawMessage) error {
switch e.port {
case "465":
return e.sendTLS(emailData.To, []byte(emailData.Body))
default:
return e.send(emailData.To, []byte(emailData.Body))
}
}
func (e *EmailService) send(to string, msg []byte) error { func (e *EmailService) send(to string, msg []byte) error {
c, err := e.dial(e.host + ":" + e.port) c, err := e.dial(e.host + ":" + e.port)
if err != nil { if err != nil {
return fmt.Errorf("DIAL: %s", err) return fmt.Errorf("DIAL: %s", err)
} }
if e.tlsconfig != nil {
if err = c.StartTLS(e.tlsconfig); err != nil { if err = c.StartTLS(e.tlsconfig); err != nil {
return fmt.Errorf("c.StartTLS: %s", err) return fmt.Errorf("c.StartTLS: %s", err)
} }
}
// Auth // Auth
if e.auth != nil {
if err = c.Auth(e.auth); err != nil { if err = c.Auth(e.auth); err != nil {
return fmt.Errorf("c.Auth: %s", err) return fmt.Errorf("c.Auth: %s", err)
} }
}
// To && From // To && From
if err = c.Mail(e.from); err != nil { if err = c.Mail(e.from); err != nil {
@@ -341,3 +388,16 @@ func (m message) withAttachments(body string, attachments []EmailAttachment) ([]
message.WriteString("--" + delimeter + "--") // End the message message.WriteString("--" + delimeter + "--") // End the message
return message.Bytes(), nil return message.Bytes(), nil
} }
type EmailAttachment struct {
File io.Reader
Title string
}
func (e EmailAttachment) ReadContent() ([]byte, error) {
bts, err := io.ReadAll(e.File)
if err != nil {
return nil, fmt.Errorf("error loading attachment: %s", err)
}
return bts, nil
}

View File

@@ -45,7 +45,7 @@ func TestNewConfig_MissingEnvFile(t *testing.T) {
func TestMockSendEmail(t *testing.T) { func TestMockSendEmail(t *testing.T) {
service := NewMockMailService(func(params ...interface{}) {}) service := NewMockMailService(func(params ...interface{}) {})
emailData := EmailMessage{ emailData := MessageWithAttachments{
To: "test@example.com", To: "test@example.com",
Subject: "Test Email", Subject: "Test Email",
Body: "This is a test email.", Body: "This is a test email.",
@@ -60,7 +60,7 @@ func TestMockSendEmail(t *testing.T) {
func TestNewInsecure(t *testing.T) { func TestNewInsecure(t *testing.T) {
cfg := newConfig(".env.test") cfg := newConfig(".env.test")
mailSrv := NewInsecure(MailServiceConfig{ mailSrv := NewInsecure(SecureConfig{
Auth: smtp.PlainAuth("", cfg.MailUser, cfg.MailPassword, cfg.MailHost), Auth: smtp.PlainAuth("", cfg.MailUser, cfg.MailPassword, cfg.MailHost),
Host: cfg.MailHost, Host: cfg.MailHost,
Port: cfg.MailPort, Port: cfg.MailPort,
@@ -68,7 +68,7 @@ func TestNewInsecure(t *testing.T) {
}) })
t.Run("TestSendEmail", func(t *testing.T) { t.Run("TestSendEmail", func(t *testing.T) {
data := EmailMessage{ data := MessageWithAttachments{
To: cfg.MailTo, To: cfg.MailTo,
Subject: "Mail Sender", Subject: "Mail Sender",
Body: "Hello this is a test email", Body: "Hello this is a test email",
@@ -89,7 +89,7 @@ func TestNewInsecure(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer reader3.Close() defer reader3.Close()
data := EmailMessage{ data := MessageWithAttachments{
To: cfg.MailTo, To: cfg.MailTo,
Subject: "Mail Sender", Subject: "Mail Sender",
Body: "Hello this is a test email", Body: "Hello this is a test email",
@@ -120,7 +120,7 @@ func TestNewInsecure(t *testing.T) {
}) })
t.Run("TestSendEmail_InvalidRecipient", func(t *testing.T) { t.Run("TestSendEmail_InvalidRecipient", func(t *testing.T) {
data := EmailMessage{ data := MessageWithAttachments{
To: "invalid_email", To: "invalid_email",
Subject: "Test Email", Subject: "Test Email",
Body: "This is a test email.", Body: "This is a test email.",
@@ -131,13 +131,13 @@ func TestNewInsecure(t *testing.T) {
t.Run("TestSendEmail_FailedAuthentication", func(t *testing.T) { t.Run("TestSendEmail_FailedAuthentication", func(t *testing.T) {
// set up authentication to fail // set up authentication to fail
mailSrv := NewInsecure(MailServiceConfig{ mailSrv := NewInsecure(SecureConfig{
Auth: smtp.PlainAuth("", "wronguser", "wrongpassword", cfg.MailHost), Auth: smtp.PlainAuth("", "wronguser", "wrongpassword", cfg.MailHost),
Host: cfg.MailHost, Host: cfg.MailHost,
Port: cfg.MailPort, Port: cfg.MailPort,
From: cfg.MailFrom, From: cfg.MailFrom,
}) })
data := EmailMessage{ data := MessageWithAttachments{
To: cfg.MailTo, To: cfg.MailTo,
Subject: "Test Email", Subject: "Test Email",
Body: "This is a test email.", Body: "This is a test email.",
@@ -150,7 +150,7 @@ func TestNewInsecure(t *testing.T) {
func TestSecure(t *testing.T) { func TestSecure(t *testing.T) {
cfg := newConfig(".env.test") cfg := newConfig(".env.test")
emailService := NewSecure(MailServiceConfig{ emailService := NewSecure(SecureConfig{
Auth: smtp.PlainAuth("", cfg.MailUser, cfg.MailPassword, cfg.MailHost), Auth: smtp.PlainAuth("", cfg.MailUser, cfg.MailPassword, cfg.MailHost),
Host: cfg.MailHost, Host: cfg.MailHost,
Port: cfg.MailPort, Port: cfg.MailPort,
@@ -172,7 +172,7 @@ func TestSecure(t *testing.T) {
} }
emailService.dial = mockDialFn emailService.dial = mockDialFn
data := EmailMessage{ data := MessageWithAttachments{
To: cfg.MailTo, To: cfg.MailTo,
Subject: "Mail Sender", Subject: "Mail Sender",
Body: "Hello this is a test email", Body: "Hello this is a test email",

View File

@@ -1,26 +0,0 @@
package email
import (
"fmt"
"io"
)
type EmailMessage struct {
To string
Subject string
Body string
Attachments []EmailAttachment
}
type EmailAttachment struct {
File io.Reader
Title string
}
func (e EmailAttachment) ReadContent() ([]byte, error) {
bts, err := io.ReadAll(e.File)
if err != nil {
return nil, fmt.Errorf("error loading attachment: %s", err)
}
return bts, nil
}