diff --git a/mailer/mailer.go b/mailer/mailer.go index 7329888..7c977de 100644 --- a/mailer/mailer.go +++ b/mailer/mailer.go @@ -21,14 +21,14 @@ func Init() { func NewMail(mail structs.Mail) error { gocnjhelper.LogDebug("NEW MAIL") - body, err := mail.RenderTemplate() + htmlBody, textBody, err := mail.RenderTemplate() if err != nil { gocnjhelper.LogErrorf("Failed to render template, err: %s", err) return err } - if err = mail.Send(body); err != nil { + if err = mail.Send(htmlBody, textBody); err != nil { gocnjhelper.LogErrorf("Failed to send mail, err: %s", err) return err } diff --git a/mailer/template.go b/mailer/template.go index 5866b1c..2a8e70c 100644 --- a/mailer/template.go +++ b/mailer/template.go @@ -31,7 +31,13 @@ func loadTemplateFiles() { gocnjhelper.LogDebug("STARTING IMPORTING TEMPLATE FILES") for templateName := range cache.Templates.Templates { - data, err := os.ReadFile(config.Cfg.Templates.FolderPath + templateName + ".html") + dataHtml, err := os.ReadFile(config.Cfg.Templates.FolderPath + templateName + ".html") + + if err != nil { + gocnjhelper.LogFatalf("Failed to read file, err: %s", err) + } + + dataTxt, err := os.ReadFile(config.Cfg.Templates.FolderPath + templateName + ".txt") if err != nil { gocnjhelper.LogFatalf("Failed to read file, err: %s", err) @@ -45,7 +51,11 @@ func loadTemplateFiles() { // cache.BodyTemplates[templateName] = []byte(minifiedHtml) - cache.BodyTemplates[templateName] = []byte(data) + //cache.BodyTemplates[templateName] = []byte(data) + cache.BodyTemplates[templateName] = cache.BodyContentTemplate{ + HTML: dataHtml, + PlainText: dataTxt, + } } gocnjhelper.LogDebug("FINISHED IMPORTING TEMPLATE FILES") diff --git a/modules/cache/cache.go b/modules/cache/cache.go index 1b7e3d3..5ce06d5 100644 --- a/modules/cache/cache.go +++ b/modules/cache/cache.go @@ -6,7 +6,12 @@ type templates struct { Templates map[string]map[string]map[string]string } +type BodyContentTemplate struct { + HTML []byte + PlainText []byte // fallback if html is not supported +} + var Templates templates -var BodyTemplates = make(map[string][]byte) +var BodyTemplates = make(map[string]BodyContentTemplate) var SmtpAuth smtp.Auth diff --git a/modules/structs/mail.go b/modules/structs/mail.go index c0aa58b..d7f6a97 100644 --- a/modules/structs/mail.go +++ b/modules/structs/mail.go @@ -24,16 +24,30 @@ type Mail struct { BodyData interface{} } -func (m *Mail) Send(body string) error { +func (m *Mail) Send(htmlBody, textBody string) error { cfg := config.Cfg.Mail + // Generate unique message ID + messageID := uuid.New().String() + "@" + strings.Split(cfg.FromEmail, "@")[1] + + // Generate unique boundary string + boundary := "boundary" + uuid.New().String() + + // Create MIME message with multipart/alternative content type msg := "From: " + cfg.FromName + " <" + cfg.FromEmail + ">\n" + "To: " + strings.Join(m.To, ",") + "\n" + "Subject: " + m.Subject + "\n" + "Date: " + time.Now().Format("Mon, 02 Jan 2006 15:04:05 -0700") + "\n" + - "Message-ID: <" + uuid.New().String() + "@" + strings.Split(cfg.FromEmail, "@")[1] + ">\n" + - "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" + - body + "Message-ID: <" + messageID + ">\n" + + "MIME-version: 1.0;\n" + + "Content-Type: multipart/alternative; boundary=" + boundary + "\n\n" + + "--" + boundary + "\n" + + "Content-Type: text/plain; charset=\"UTF-8\"\n\n" + + textBody + "\n\n" + + "--" + boundary + "\n" + + "Content-Type: text/html; charset=\"UTF-8\"\n\n" + + htmlBody + "\n\n" + + "--" + boundary + "--" err := smtp.SendMail(cfg.Host+":"+cfg.Port, cache.SmtpAuth, cfg.FromEmail, m.To, []byte(msg)) @@ -46,6 +60,94 @@ func (m *Mail) Send(body string) error { return nil } +/* +func (m *Mail) Send(body string) error { + cfg := config.Cfg.Mail + + msg := "From: " + cfg.FromName + " <" + cfg.FromEmail + ">\n" + + "To: " + strings.Join(m.To, ",") + "\n" + + "Subject: " + m.Subject + "\n" + + "Date: " + time.Now().Format("Mon, 02 Jan 2006 15:04:05 -0700") + "\n" + + "Message-ID: <" + uuid.New().String() + "@" + strings.Split(cfg.FromEmail, "@")[1] + ">\n" + + "MIME-version: 1.0;\nContent-Type: multipart/alternative; charset=\"UTF-8\";\n\n" + + body + + err := smtp.SendMail(cfg.Host+":"+cfg.Port, cache.SmtpAuth, cfg.FromEmail, m.To, []byte(msg)) + + if err != nil { + gocnjhelper.LogErrorf("smtp error: %s", err) + return err + } + + gocnjhelper.LogDebugf("SEND MAIL %s", msg) + return nil +} */ + +func (m *Mail) RenderTemplate() (htmlBody, textBody string, err error) { + body := cache.BodyTemplates[m.TemplateId] + + for templateName, templateData := range cache.Templates.Templates { + // Skipping templates if the template ID is not the expected one + if templateName != m.TemplateId { + continue + } + + gocnjhelper.LogDebugf("RENDER TEMPLATE %s %s", templateName, templateData) + + m.Subject = templateData["mailSubject"][m.LanguageId] + + // occurs if the requested language code does not exist + if m.Subject == "" { + m.Subject = templateData["mailSubject"][config.Cfg.DefaultLanguageCode] + } + + // replace body %values% with values in templates config + for key, value := range templateData { + if key == "mailSubject" { + continue + } + + v := value[m.LanguageId] + + // occurs if the requested language code does not exist + if v == "" { + v = value[config.Cfg.DefaultLanguageCode] + } + + // escaping so that umlauts are displayed correctly + if m.LanguageId == "de" { + v = escaper.EscapeHtmlEntites(v) + } + + body.HTML = []byte(strings.Replace(string(body.HTML), "%"+key+"%", v, -1)) + } + } + + // The subject line of an email is an independent header and utf 8 must be set for it + // https://ncona.com/2011/06/using-utf-8-characters-on-an-e-mail-subject/ + if m.LanguageId == "de" { + m.Subject = EncodeBase64(m.Subject) + } + + // Separate rendering for HTML and text bodies + htmlTemplate := template.Must(template.New("").Parse(string(body.HTML))) + textTemplate := template.Must(template.New("").Parse(string(body.PlainText))) + + htmlBuf := new(bytes.Buffer) + textBuf := new(bytes.Buffer) + + if err := htmlTemplate.Execute(htmlBuf, m.BodyData); err != nil { + return "", "", err + } + + if err := textTemplate.Execute(textBuf, m.BodyData); err != nil { + return "", "", err + } + + return htmlBuf.String(), textBuf.String(), nil +} + +/* func (m *Mail) RenderTemplate() (string, error) { body := cache.BodyTemplates[m.TemplateId] @@ -82,19 +184,17 @@ func (m *Mail) RenderTemplate() (string, error) { v = escaper.EscapeHtmlEntites(v) } - body = []byte(strings.Replace(string(body), "%"+key+"%", v, -1)) + body.HTML = []byte(strings.Replace(string(body.HTML), "%"+key+"%", v, -1)) } } // The subject line of an email is an independent header and utf 8 must be set for it // https://ncona.com/2011/06/using-utf-8-characters-on-an-e-mail-subject/ if m.LanguageId == "de" { - //m.Subject = "=?utf-8?B?" + base64.StdEncoding.EncodeToString([]byte(m.Subject)) + "?=" - m.Subject = EncodeBase64(m.Subject) } - t := template.Must(template.New("").Parse(string(body))) + t := template.Must(template.New("").Parse(string(body.HTML))) buf := new(bytes.Buffer) @@ -103,7 +203,7 @@ func (m *Mail) RenderTemplate() (string, error) { } return buf.String(), nil -} +} */ // https://ncona.com/2011/06/using-utf-8-characters-on-an-e-mail-subject/ func EncodeBase64(s string) string { diff --git a/templates/SignUpSecondStep.html b/templates/SignUpSecondStep.html deleted file mode 100644 index b36dec4..0000000 --- a/templates/SignUpSecondStep.html +++ /dev/null @@ -1,7 +0,0 @@ - - -
-%informationText%
- - \ No newline at end of file diff --git a/templates/emailVerification.html b/templates/emailVerification.html deleted file mode 100644 index 1f65986..0000000 --- a/templates/emailVerification.html +++ /dev/null @@ -1,9 +0,0 @@ - - - -%informationText%
-{{.activation_code}}
-%codeUse%
- - \ No newline at end of file diff --git a/templates/emailVerification2.txt b/templates/emailVerification2.txt new file mode 100644 index 0000000..e1e5e3d --- /dev/null +++ b/templates/emailVerification2.txt @@ -0,0 +1,12 @@ +%title% +%header% +%text1% +{{.verifyURL}} +%cancelText% +%yourAppointment% +%appointment1% +%appointment2% +%appointment3% +{{.address}} +%footer% +%dsgvo% \ No newline at end of file diff --git a/templates/emailVerifyFailed.txt b/templates/emailVerifyFailed.txt new file mode 100644 index 0000000..bf06a59 --- /dev/null +++ b/templates/emailVerifyFailed.txt @@ -0,0 +1,11 @@ +%title% +%header% +%text1% + +%yourAppointment% +%appointment1% +%appointment2% +%appointment3% +{{.address}} + +%dsgvo% \ No newline at end of file diff --git a/templates/newUserSignIn.html b/templates/newUserSignIn.html deleted file mode 100644 index b36dec4..0000000 --- a/templates/newUserSignIn.html +++ /dev/null @@ -1,7 +0,0 @@ - - - -%informationText%
- - \ No newline at end of file diff --git a/templates/templates.json b/templates/templates.json index f166885..3bae42d 100644 --- a/templates/templates.json +++ b/templates/templates.json @@ -1,23 +1,5 @@ { "templates": { - "emailVerification": { - "mailSubject": { - "en": "Your email confirmation code", - "de": "Ihr E-Mail-Bestätigungscode" - }, - "header": { - "en": "Good day,", - "de": "Guten Tag," - }, - "informationText": { - "en": "to confirm your identity we need to verify your email.", - "de": "um deine Identität zu bestätigen müssen wir deine E-Mail verifizieren." - }, - "codeUse": { - "en": "Please enter this code in the app", - "de": "Bitte gebe diesen Code in der App ein" - } - }, "emailVerifyFailed": { "mailSubject": { "de": "Ihre Buchung wurde storniert" @@ -89,34 +71,6 @@ "dsgvo": { "de": "Datenschutzerklärung" } - }, - "newUserSignIn": { - "mailSubject": { - "en": "A new sign-in was detected", - "de": "Neue Anmeldung wurde festgestellt" - }, - "header": { - "en": "Good day,", - "de": "Guten Tag," - }, - "informationText": { - "en": "a new sign-in on {{.device}} was detected", - "de": "eine neue Anmeldung auf {{.device}} wurde festgestellt" - } - }, - "SignUpSecondStep": { - "mailSubject": { - "en": "Welcome", - "de": "Willkommen" - }, - "header": { - "en": "Good day {{.account_name}},", - "de": "Guten Tag {{.account_name}}," - }, - "informationText": { - "en": "nice that you are here! Your account is created and you can start right away", - "de": "schön das du da bist! Dein Account ist erstellt und du kannst sofort loslegen" - } } } } \ No newline at end of file