github.com/greenpau/go-authcrunch@v1.0.50/pkg/messaging/email_send.go (about) 1 // Copyright 2022 Paul Greenberg greenpau@outlook.com 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package messaging 16 17 import ( 18 "fmt" 19 "github.com/emersion/go-sasl" 20 "github.com/emersion/go-smtp" 21 "github.com/greenpau/go-authcrunch/pkg/credentials" 22 "github.com/greenpau/go-authcrunch/pkg/errors" 23 "github.com/greenpau/go-authcrunch/pkg/util" 24 "strings" 25 "time" 26 ) 27 28 // EmailProviderSendInput is input for EmailProvider.Send function. 29 type EmailProviderSendInput struct { 30 Subject string `json:"subject,omitempty" xml:"subject,omitempty" yaml:"subject,omitempty"` 31 Body string `json:"body,omitempty" xml:"body,omitempty" yaml:"body,omitempty"` 32 Recipients []string `json:"recipients,omitempty" xml:"recipients,omitempty" yaml:"recipients,omitempty"` 33 Credentials *credentials.Generic `json:"credentials,omitempty" xml:"credentials,omitempty" yaml:"credentials,omitempty"` 34 } 35 36 // Send sends an email message. 37 func (e *EmailProvider) Send(req *EmailProviderSendInput) error { 38 dial := smtp.Dial 39 if e.Protocol == "smtps" { 40 dial = func(addr string) (*smtp.Client, error) { 41 return smtp.DialTLS(addr, nil) 42 } 43 } 44 45 c, err := dial(e.Address) 46 if err != nil { 47 return err 48 } 49 defer c.Close() 50 51 if found, _ := c.Extension("STARTTLS"); found { 52 if err := c.StartTLS(nil); err != nil { 53 return err 54 } 55 } 56 57 if !e.Passwordless && req.Credentials != nil { 58 if found, _ := c.Extension("AUTH"); !found { 59 return errors.ErrMessagingProviderAuthUnsupported 60 } 61 auth := sasl.NewPlainClient("", req.Credentials.Username, req.Credentials.Password) 62 if err := c.Auth(auth); err != nil { 63 return err 64 } 65 } 66 67 if err := c.Mail(e.SenderEmail, nil); err != nil { 68 return err 69 } 70 71 for _, rcpt := range req.Recipients { 72 if err := c.Rcpt(rcpt, nil); err != nil { 73 return err 74 } 75 } 76 77 sender := e.SenderEmail 78 if e.SenderName != "" { 79 sender = `"` + e.SenderName + `" <` + e.SenderEmail + ">" 80 } 81 82 msg := "MIME-Version: 1.0\n" 83 msg += "Date: " + time.Now().Format(time.RFC1123Z) + "\n" 84 msg += "From: " + sender + "\n" 85 msg += "Subject: " + req.Subject + "\n" 86 msg += "Thread-Topic: Account Registration." + "\n" 87 msg += "Message-ID: <" + util.GetRandomString(64) + "." + e.SenderEmail + ">" + "\n" 88 msg += `To: ` + strings.Join(req.Recipients, ", ") + "\n" 89 90 if len(e.BlindCarbonCopy) > 0 { 91 bccRcpts := dedupRcpt(req.Recipients, e.BlindCarbonCopy) 92 if len(bccRcpts) > 0 { 93 msg += "Bcc: " + strings.Join(bccRcpts, ", ") + "\n" 94 } 95 } 96 97 msg += "Content-Transfer-Encoding: quoted-printable" + "\n" 98 msg += `Content-Type: text/html; charset="utf-8"` + "\n" 99 100 msg += "\r\n" + req.Body 101 102 // Write email subject body. 103 wc, err := c.Data() 104 if err != nil { 105 return err 106 } 107 _, err = fmt.Fprintf(wc, msg) 108 if err != nil { 109 return err 110 } 111 112 if err := wc.Close(); err != nil { 113 return err 114 } 115 116 // Close connection. 117 if err := c.Quit(); err != nil { 118 return err 119 } 120 121 return nil 122 } 123 124 func dedupRcpt(arr1, arr2 []string) []string { 125 var output []string 126 m := make(map[string]interface{}) 127 for _, s := range arr1 { 128 m[s] = true 129 } 130 131 for _, s := range arr2 { 132 if _, exists := m[s]; exists { 133 continue 134 } 135 output = append(output, s) 136 } 137 return output 138 }