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