Skip to content

Commit 3938608

Browse files
committed
feat(pkg): init send email
1 parent 0879dbc commit 3938608

File tree

2 files changed

+619
-0
lines changed

2 files changed

+619
-0
lines changed

pkg/email/email.go

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package email
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"errors"
7+
"fmt"
8+
"html/template"
9+
"net/smtp"
10+
"strings"
11+
12+
"github.com/hammer-code/lms-be/pkg/ngelog"
13+
)
14+
15+
// SMTPSender interface for dependency injection
16+
type SMTPSender interface {
17+
SendMail(addr string, a smtp.Auth, from string, to []string, msg []byte) error
18+
}
19+
20+
// DefaultSMTPSender implements SMTPSender using the real smtp.SendMail
21+
type DefaultSMTPSender struct{}
22+
23+
func (s *DefaultSMTPSender) SendMail(addr string, a smtp.Auth, from string, to []string, msg []byte) error {
24+
return smtp.SendMail(addr, a, from, to, msg)
25+
}
26+
27+
type SMTP struct {
28+
Email string
29+
Password string
30+
Host string
31+
Port string
32+
}
33+
34+
type Receiver struct {
35+
Email string
36+
Data any
37+
}
38+
39+
type PayloadEmail struct {
40+
HtmlTemplate *template.Template
41+
Subject string
42+
Mime string
43+
Sender SMTP
44+
Receiver []Receiver
45+
CC []string
46+
smtpSender SMTPSender // injected SMTP sender
47+
}
48+
49+
type failedToSend struct {
50+
Subject string
51+
Receiver Receiver
52+
}
53+
54+
// Updated constructor with optional SMTPSender
55+
func NewSendEmail(ctx context.Context, smtp SMTP, mime string, subject string, HtmlTemplate *template.Template) PayloadEmail {
56+
return PayloadEmail{
57+
HtmlTemplate: HtmlTemplate,
58+
Subject: subject,
59+
Mime: mime,
60+
Sender: smtp,
61+
CC: []string{},
62+
smtpSender: &DefaultSMTPSender{}, // Use default implementation
63+
}
64+
}
65+
66+
// New constructor for testing that allows injecting the SMTPSender
67+
func NewSendEmailWithSender(ctx context.Context, smtp SMTP, mime string, subject string, HtmlTemplate *template.Template, sender SMTPSender) PayloadEmail {
68+
if sender == nil {
69+
sender = &DefaultSMTPSender{}
70+
}
71+
return PayloadEmail{
72+
HtmlTemplate: HtmlTemplate,
73+
Subject: subject,
74+
Mime: mime,
75+
Sender: smtp,
76+
CC: []string{},
77+
smtpSender: sender,
78+
}
79+
}
80+
81+
func (p *PayloadEmail) AddReceiver(ctx context.Context, receiver Receiver) error {
82+
if receiver.Email == "" {
83+
ngelog.Error(ctx, "email cannot be null", nil)
84+
return errors.New("email cannot be null")
85+
}
86+
87+
p.Receiver = append(p.Receiver, receiver)
88+
return nil
89+
}
90+
91+
func (p *PayloadEmail) AddCC(ctx context.Context, ccEmail string) error {
92+
if ccEmail == "" {
93+
ngelog.Error(ctx, "cc email cannot be null", nil)
94+
return errors.New("cc email cannot be null")
95+
}
96+
97+
p.CC = append(p.CC, ccEmail)
98+
return nil
99+
}
100+
101+
func (p *PayloadEmail) AddMultipleCC(ctx context.Context, ccEmails []string) error {
102+
for _, email := range ccEmails {
103+
if email == "" {
104+
ngelog.Error(ctx, "cc email cannot be null", nil)
105+
return errors.New("cc email cannot be null")
106+
}
107+
}
108+
109+
p.CC = append(p.CC, ccEmails...)
110+
return nil
111+
}
112+
113+
// Updated SendEmail method using the injected sender
114+
func (p PayloadEmail) SendEmail(ctx context.Context) {
115+
var failedSendedEmails []failedToSend
116+
for _, receiver := range p.Receiver {
117+
118+
// Create a temporary buffer to hold the parsed HTML
119+
var bodyContent string
120+
bodyBuffer := new(bytes.Buffer)
121+
// Execute the template with data
122+
if err := p.HtmlTemplate.Execute(bodyBuffer, receiver.Data); err != nil {
123+
ngelog.Error(ctx, fmt.Sprintf("Error executing template for %s", receiver.Email), err)
124+
failedSendedEmails = append(failedSendedEmails, failedToSend{
125+
Subject: p.Subject,
126+
Receiver: receiver,
127+
})
128+
continue
129+
}
130+
131+
bodyContent = bodyBuffer.String()
132+
133+
// Build message headers
134+
headers := fmt.Sprintf("To: %s\r\n", receiver.Email)
135+
136+
// Add CC header if there are CC recipients
137+
if len(p.CC) > 0 {
138+
headers += fmt.Sprintf("Cc: %s\r\n", strings.Join(p.CC, ", "))
139+
}
140+
141+
headers += fmt.Sprintf("Subject: %s\r\n%s\r\n", p.Subject, p.Mime)
142+
143+
message := []byte(headers + bodyContent + "\r\n")
144+
145+
auth := smtp.PlainAuth("", p.Sender.Email, p.Sender.Password, p.Sender.Host)
146+
147+
// Combine recipient and CC addresses for SMTP
148+
allRecipients := []string{receiver.Email}
149+
allRecipients = append(allRecipients, p.CC...)
150+
151+
host := fmt.Sprintf("%s:%s", p.Sender.Host, p.Sender.Port)
152+
153+
// Use the injected SMTP sender
154+
if err := p.smtpSender.SendMail(host, auth, p.Sender.Email, allRecipients, message); err != nil {
155+
ngelog.Error(ctx, fmt.Sprintf("failed to send to %s", receiver.Email), err)
156+
failedSendedEmails = append(failedSendedEmails, failedToSend{
157+
Subject: p.Subject,
158+
Receiver: receiver,
159+
})
160+
continue
161+
}
162+
163+
ngelog.Info(ctx, "success send email", ngelog.AddFields{
164+
"email": receiver.Email,
165+
"subject": p.Subject,
166+
"cc": p.CC,
167+
})
168+
}
169+
170+
if len(failedSendedEmails) > 0 {
171+
ngelog.Error(ctx, "failed to send email", nil, ngelog.AddFields{
172+
"data": failedSendedEmails,
173+
})
174+
}
175+
}

0 commit comments

Comments
 (0)