mail.go
| 1 | package web |
| 2 | |
| 3 | import ( |
| 4 | "bytes" |
| 5 | "html/template" |
| 6 | "net/http" |
| 7 | "net/url" |
| 8 | "strings" |
| 9 | |
| 10 | "forge.gpl-nas.de/physiobase/internal/mailer" |
| 11 | "forge.gpl-nas.de/physiobase/internal/store" |
| 12 | ) |
| 13 | |
| 14 | // E-Mail-Clients ignorieren externe CSS-Dateien → schlichte Inline-Stile. |
| 15 | var mailHTML = template.Must(template.New("mail").Parse(`<!doctype html> |
| 16 | <html lang="de"><head><meta charset="utf-8"><title>{{.Title}}</title></head> |
| 17 | <body style="margin:0;padding:0;background:#f4f6f8;"> |
| 18 | <div style="max-width:640px;margin:0 auto;padding:24px;font-family:-apple-system,'Segoe UI',Roboto,Arial,sans-serif;font-size:16px;line-height:1.55;color:#1d2a33;background:#ffffff;"> |
| 19 | <h1 style="font-size:22px;margin:0 0 4px;color:#16a085;">{{.Title}}</h1> |
| 20 | <p style="margin:0 0 18px;color:#5c6b75;font-size:14px;">{{.Date}}</p> |
| 21 | <p>Guten Tag {{.Name}},</p> |
| 22 | <p>hier ist Ihre Zusammenfassung zum Nachlesen.</p> |
| 23 | <div>{{.Body}}</div> |
| 24 | <p style="margin-top:24px;">Viele Grüße<br>{{.Practice}}</p> |
| 25 | <p style="margin-top:24px;font-size:12px;color:#8a979f;">Diese Nachricht enthält persönliche Gesundheitsinformationen und ist nur für {{.Name}} bestimmt.</p> |
| 26 | </div></body></html>`)) |
| 27 | |
| 28 | func mailText(name, title, date, body, practice string) string { |
| 29 | var b strings.Builder |
| 30 | b.WriteString("Guten Tag " + name + ",\n\nhier ist Ihre Zusammenfassung zum Nachlesen.\n\n") |
| 31 | b.WriteString(title + " (" + date + ")\n" + strings.Repeat("=", len([]rune(title))) + "\n\n") |
| 32 | b.WriteString(body + "\n\nViele Grüße\n" + practice + "\n\n") |
| 33 | b.WriteString("Diese Nachricht enthält persönliche Gesundheitsinformationen und ist nur für " + name + " bestimmt.\n") |
| 34 | return b.String() |
| 35 | } |
| 36 | |
| 37 | // mailBlockReason: warum (noch) nicht gesendet werden kann; leer = alles bereit. |
| 38 | func (s *Server) mailBlockReason(p store.Patient) string { |
| 39 | switch { |
| 40 | case !s.cfg.SMTP.Enabled(): |
| 41 | return "Der E-Mail-Versand ist nicht eingerichtet (PHYSIOBASE_SMTP_* in .env)." |
| 42 | case p.Email == "": |
| 43 | return "Für diesen Patienten ist keine E-Mail-Adresse hinterlegt." |
| 44 | case !mailer.ValidAddress(p.Email): |
| 45 | return "Die hinterlegte E-Mail-Adresse ist ungültig." |
| 46 | case !p.EmailConsent: |
| 47 | return "Es fehlt die Einwilligung in den Versand per E-Mail (Stammdaten des Patienten)." |
| 48 | } |
| 49 | return "" |
| 50 | } |
| 51 | |
| 52 | func (s *Server) sendSummary(w http.ResponseWriter, r *http.Request) { |
| 53 | ctx := r.Context() |
| 54 | x, p, ok := s.loadSummary(w, r) |
| 55 | if !ok { |
| 56 | return |
| 57 | } |
| 58 | if reason := s.mailBlockReason(p); reason != "" { |
| 59 | redirect(w, r, "/zusammenfassung/%d?fehler=%s#senden", x.ID, url.QueryEscape(reason)) |
| 60 | return |
| 61 | } |
| 62 | body := Printable(x.Body) |
| 63 | var html bytes.Buffer |
| 64 | if err := mailHTML.Execute(&html, map[string]any{ |
| 65 | "Title": x.Title, "Date": deDate(x.Date), "Name": p.Name, "Practice": s.cfg.PracticeName, |
| 66 | "Body": funcs["md"].(func(string) template.HTML)(body), |
| 67 | }); err != nil { |
| 68 | s.fail(w, r, err) |
| 69 | return |
| 70 | } |
| 71 | msg := mailer.Message{ |
| 72 | To: p.Email, Subject: x.Title, FromName: s.cfg.PracticeName, |
| 73 | Text: mailText(p.Name, x.Title, deDate(x.Date), body, s.cfg.PracticeName), HTML: html.String(), |
| 74 | } |
| 75 | err := mailer.Send(ctx, s.cfg.SMTP, msg) |
| 76 | entry := store.MailLog{Recipient: p.Email, Subject: x.Title, OK: err == nil} |
| 77 | if err != nil { |
| 78 | entry.Error = err.Error() |
| 79 | s.log.Error("e-mail fehlgeschlagen", "zusammenfassung", x.ID, "err", err) |
| 80 | } else { |
| 81 | s.log.Info("e-mail gesendet", "zusammenfassung", x.ID, "patient", p.ID) |
| 82 | } |
| 83 | if lerr := s.st.LogMail(ctx, x.ID, p.ID, entry); lerr != nil { |
| 84 | s.log.Error("versandprotokoll", "err", lerr) |
| 85 | } |
| 86 | if err != nil { |
| 87 | redirect(w, r, "/zusammenfassung/%d?fehler=%s#senden", x.ID, url.QueryEscape("Senden fehlgeschlagen: "+err.Error())) |
| 88 | return |
| 89 | } |
| 90 | redirect(w, r, "/zusammenfassung/%d?gesendet=1#senden", x.ID) |
| 91 | } |
| 92 |