email.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913
  1. package mail
  2. import (
  3. "bytes"
  4. "crypto/tls"
  5. "encoding/base64"
  6. "errors"
  7. "fmt"
  8. "io/ioutil"
  9. "mime"
  10. "net"
  11. "net/mail"
  12. "net/textproto"
  13. "path/filepath"
  14. "time"
  15. )
  16. // Email represents an email message.
  17. type Email struct {
  18. from string
  19. sender string
  20. replyTo string
  21. returnPath string
  22. recipients []string
  23. headers textproto.MIMEHeader
  24. parts []part
  25. attachments []*file
  26. inlines []*file
  27. Charset string
  28. Encoding encoding
  29. Error error
  30. SMTPServer *smtpClient
  31. }
  32. /*
  33. SMTPServer represents a SMTP Server
  34. If authentication is CRAM-MD5 then the Password is the Secret
  35. */
  36. type SMTPServer struct {
  37. Authentication authType
  38. Encryption encryption
  39. Username string
  40. Password string
  41. ConnectTimeout time.Duration
  42. SendTimeout time.Duration
  43. Host string
  44. Port int
  45. KeepAlive bool
  46. }
  47. //SMTPClient represents a SMTP Client for send email
  48. type SMTPClient struct {
  49. Client *smtpClient
  50. KeepAlive bool
  51. SendTimeout time.Duration
  52. }
  53. // part represents the different content parts of an email body.
  54. type part struct {
  55. contentType string
  56. body *bytes.Buffer
  57. }
  58. // file represents the files that can be added to the email message.
  59. type file struct {
  60. filename string
  61. mimeType string
  62. data []byte
  63. }
  64. type encryption int
  65. const (
  66. // EncryptionNone uses no encryption when sending email
  67. EncryptionNone encryption = iota
  68. // EncryptionSSL sets encryption type to SSL when sending email
  69. EncryptionSSL
  70. // EncryptionTLS sets encryption type to TLS when sending email
  71. EncryptionTLS
  72. )
  73. var encryptionTypes = [...]string{"None", "SSL", "TLS"}
  74. func (encryption encryption) string() string {
  75. return encryptionTypes[encryption]
  76. }
  77. type encoding int
  78. const (
  79. // EncodingNone turns off encoding on the message body
  80. EncodingNone encoding = iota
  81. // EncodingBase64 sets the message body encoding to base64
  82. EncodingBase64
  83. // EncodingQuotedPrintable sets the message body encoding to quoted-printable
  84. EncodingQuotedPrintable
  85. )
  86. var encodingTypes = [...]string{"binary", "base64", "quoted-printable"}
  87. func (encoding encoding) string() string {
  88. return encodingTypes[encoding]
  89. }
  90. type contentType int
  91. const (
  92. // TextPlain sets body type to text/plain in message body
  93. TextPlain contentType = iota
  94. // TextHTML sets body type to text/html in message body
  95. TextHTML
  96. )
  97. var contentTypes = [...]string{"text/plain", "text/html"}
  98. func (contentType contentType) string() string {
  99. return contentTypes[contentType]
  100. }
  101. type authType int
  102. const (
  103. // AuthPlain implements the PLAIN authentication
  104. AuthPlain authType = iota
  105. // AuthLogin implements the LOGIN authentication
  106. AuthLogin
  107. // AuthCRAMMD5 implements the CRAM-MD5 authentication
  108. AuthCRAMMD5
  109. )
  110. // NewMSG creates a new email. It uses UTF-8 by default. All charsets: http://webcheatsheet.com/HTML/character_sets_list.php
  111. func NewMSG() *Email {
  112. email := &Email{
  113. headers: make(textproto.MIMEHeader),
  114. Charset: "UTF-8",
  115. Encoding: EncodingQuotedPrintable,
  116. }
  117. email.AddHeader("MIME-Version", "1.0")
  118. return email
  119. }
  120. //NewSMTPClient returns the client for send email
  121. func NewSMTPClient() *SMTPServer {
  122. server := &SMTPServer{
  123. Authentication: AuthPlain,
  124. Encryption: EncryptionNone,
  125. ConnectTimeout: 10 * time.Second,
  126. SendTimeout: 10 * time.Second,
  127. }
  128. return server
  129. }
  130. // GetError returns the first email error encountered
  131. func (email *Email) GetError() error {
  132. return email.Error
  133. }
  134. // SetFrom sets the From address.
  135. func (email *Email) SetFrom(address string) *Email {
  136. if email.Error != nil {
  137. return email
  138. }
  139. email.AddAddresses("From", address)
  140. return email
  141. }
  142. // SetSender sets the Sender address.
  143. func (email *Email) SetSender(address string) *Email {
  144. if email.Error != nil {
  145. return email
  146. }
  147. email.AddAddresses("Sender", address)
  148. return email
  149. }
  150. // SetReplyTo sets the Reply-To address.
  151. func (email *Email) SetReplyTo(address string) *Email {
  152. if email.Error != nil {
  153. return email
  154. }
  155. email.AddAddresses("Reply-To", address)
  156. return email
  157. }
  158. // SetReturnPath sets the Return-Path address. This is most often used
  159. // to send bounced emails to a different email address.
  160. func (email *Email) SetReturnPath(address string) *Email {
  161. if email.Error != nil {
  162. return email
  163. }
  164. email.AddAddresses("Return-Path", address)
  165. return email
  166. }
  167. // AddTo adds a To address. You can provide multiple
  168. // addresses at the same time.
  169. func (email *Email) AddTo(addresses ...string) *Email {
  170. if email.Error != nil {
  171. return email
  172. }
  173. email.AddAddresses("To", addresses...)
  174. return email
  175. }
  176. // AddCc adds a Cc address. You can provide multiple
  177. // addresses at the same time.
  178. func (email *Email) AddCc(addresses ...string) *Email {
  179. if email.Error != nil {
  180. return email
  181. }
  182. email.AddAddresses("Cc", addresses...)
  183. return email
  184. }
  185. // AddBcc adds a Bcc address. You can provide multiple
  186. // addresses at the same time.
  187. func (email *Email) AddBcc(addresses ...string) *Email {
  188. if email.Error != nil {
  189. return email
  190. }
  191. email.AddAddresses("Bcc", addresses...)
  192. return email
  193. }
  194. // AddAddresses allows you to add addresses to the specified address header.
  195. func (email *Email) AddAddresses(header string, addresses ...string) *Email {
  196. if email.Error != nil {
  197. return email
  198. }
  199. found := false
  200. // check for a valid address header
  201. for _, h := range []string{"To", "Cc", "Bcc", "From", "Sender", "Reply-To", "Return-Path"} {
  202. if header == h {
  203. found = true
  204. }
  205. }
  206. if !found {
  207. email.Error = errors.New("Mail Error: Invalid address header; Header: [" + header + "]")
  208. return email
  209. }
  210. // check to see if the addresses are valid
  211. for i := range addresses {
  212. address, err := mail.ParseAddress(addresses[i])
  213. if err != nil {
  214. email.Error = errors.New("Mail Error: " + err.Error() + "; Header: [" + header + "] Address: [" + addresses[i] + "]")
  215. return email
  216. }
  217. // check for more than one address
  218. switch {
  219. case header == "From" && len(email.from) > 0:
  220. fallthrough
  221. case header == "Sender" && len(email.sender) > 0:
  222. fallthrough
  223. case header == "Reply-To" && len(email.replyTo) > 0:
  224. fallthrough
  225. case header == "Return-Path" && len(email.returnPath) > 0:
  226. email.Error = errors.New("Mail Error: There can only be one \"" + header + "\" address; Header: [" + header + "] Address: [" + addresses[i] + "]")
  227. return email
  228. default:
  229. // other address types can have more than one address
  230. }
  231. // save the address
  232. switch header {
  233. case "From":
  234. email.from = address.Address
  235. case "Sender":
  236. email.sender = address.Address
  237. case "Reply-To":
  238. email.replyTo = address.Address
  239. case "Return-Path":
  240. email.returnPath = address.Address
  241. default:
  242. // check that the address was added to the recipients list
  243. email.recipients, err = addAddress(email.recipients, address.Address)
  244. if err != nil {
  245. email.Error = errors.New("Mail Error: " + err.Error() + "; Header: [" + header + "] Address: [" + addresses[i] + "]")
  246. return email
  247. }
  248. }
  249. // make sure the from and sender addresses are different
  250. if email.from != "" && email.sender != "" && email.from == email.sender {
  251. email.sender = ""
  252. email.headers.Del("Sender")
  253. email.Error = errors.New("Mail Error: From and Sender should not be set to the same address")
  254. return email
  255. }
  256. // add all addresses to the headers except for Bcc and Return-Path
  257. if header != "Bcc" && header != "Return-Path" {
  258. // add the address to the headers
  259. email.headers.Add(header, address.String())
  260. }
  261. }
  262. return email
  263. }
  264. // addAddress adds an address to the address list if it hasn't already been added
  265. func addAddress(addressList []string, address string) ([]string, error) {
  266. // loop through the address list to check for dups
  267. for _, a := range addressList {
  268. if address == a {
  269. return addressList, errors.New("Mail Error: Address: [" + address + "] has already been added")
  270. }
  271. }
  272. return append(addressList, address), nil
  273. }
  274. type priority int
  275. const (
  276. // PriorityLow sets the email priority to Low
  277. PriorityLow priority = iota
  278. // PriorityHigh sets the email priority to High
  279. PriorityHigh
  280. )
  281. // SetPriority sets the email message priority. Use with
  282. // either "High" or "Low".
  283. func (email *Email) SetPriority(priority priority) *Email {
  284. if email.Error != nil {
  285. return email
  286. }
  287. switch priority {
  288. case PriorityLow:
  289. email.AddHeaders(textproto.MIMEHeader{
  290. "X-Priority": {"5 (Lowest)"},
  291. "X-MSMail-Priority": {"Low"},
  292. "Importance": {"Low"},
  293. })
  294. case PriorityHigh:
  295. email.AddHeaders(textproto.MIMEHeader{
  296. "X-Priority": {"1 (Highest)"},
  297. "X-MSMail-Priority": {"High"},
  298. "Importance": {"High"},
  299. })
  300. default:
  301. }
  302. return email
  303. }
  304. // SetDate sets the date header to the provided date/time.
  305. // The format of the string should be YYYY-MM-DD HH:MM:SS Time Zone.
  306. //
  307. // Example: SetDate("2015-04-28 10:32:00 CDT")
  308. func (email *Email) SetDate(dateTime string) *Email {
  309. if email.Error != nil {
  310. return email
  311. }
  312. const dateFormat = "2006-01-02 15:04:05 MST"
  313. // Try to parse the provided date/time
  314. dt, err := time.Parse(dateFormat, dateTime)
  315. if err != nil {
  316. email.Error = errors.New("Mail Error: Setting date failed with: " + err.Error())
  317. return email
  318. }
  319. email.headers.Set("Date", dt.Format(time.RFC1123Z))
  320. return email
  321. }
  322. // SetSubject sets the subject of the email message.
  323. func (email *Email) SetSubject(subject string) *Email {
  324. if email.Error != nil {
  325. return email
  326. }
  327. email.AddHeader("Subject", subject)
  328. return email
  329. }
  330. // SetBody sets the body of the email message.
  331. func (email *Email) SetBody(contentType contentType, body string) *Email {
  332. if email.Error != nil {
  333. return email
  334. }
  335. email.parts = []part{
  336. {
  337. contentType: contentType.string(),
  338. body: bytes.NewBufferString(body),
  339. },
  340. }
  341. return email
  342. }
  343. // AddHeader adds the given "header" with the passed "value".
  344. func (email *Email) AddHeader(header string, values ...string) *Email {
  345. if email.Error != nil {
  346. return email
  347. }
  348. // check that there is actually a value
  349. if len(values) < 1 {
  350. email.Error = errors.New("Mail Error: no value provided; Header: [" + header + "]")
  351. return email
  352. }
  353. switch header {
  354. case "Sender":
  355. fallthrough
  356. case "From":
  357. fallthrough
  358. case "To":
  359. fallthrough
  360. case "Bcc":
  361. fallthrough
  362. case "Cc":
  363. fallthrough
  364. case "Reply-To":
  365. fallthrough
  366. case "Return-Path":
  367. email.AddAddresses(header, values...)
  368. case "Date":
  369. if len(values) > 1 {
  370. email.Error = errors.New("Mail Error: To many dates provided")
  371. return email
  372. }
  373. email.SetDate(values[0])
  374. default:
  375. email.headers[header] = values
  376. }
  377. return email
  378. }
  379. // AddHeaders is used to add multiple headers at once
  380. func (email *Email) AddHeaders(headers textproto.MIMEHeader) *Email {
  381. if email.Error != nil {
  382. return email
  383. }
  384. for header, values := range headers {
  385. email.AddHeader(header, values...)
  386. }
  387. return email
  388. }
  389. // AddAlternative allows you to add alternative parts to the body
  390. // of the email message. This is most commonly used to add an
  391. // html version in addition to a plain text version that was
  392. // already added with SetBody.
  393. func (email *Email) AddAlternative(contentType contentType, body string) *Email {
  394. if email.Error != nil {
  395. return email
  396. }
  397. email.parts = append(email.parts,
  398. part{
  399. contentType: contentType.string(),
  400. body: bytes.NewBufferString(body),
  401. },
  402. )
  403. return email
  404. }
  405. // AddAttachment allows you to add an attachment to the email message.
  406. // You can optionally provide a different name for the file.
  407. func (email *Email) AddAttachment(file string, name ...string) *Email {
  408. if email.Error != nil {
  409. return email
  410. }
  411. if len(name) > 1 {
  412. email.Error = errors.New("Mail Error: Attach can only have a file and an optional name")
  413. return email
  414. }
  415. email.Error = email.attach(file, false, name...)
  416. return email
  417. }
  418. // AddAttachmentBase64 allows you to add an attachment in base64 to the email message.
  419. // You need provide a name for the file.
  420. func (email *Email) AddAttachmentBase64(b64File string, name string) *Email {
  421. if email.Error != nil {
  422. return email
  423. }
  424. if len(name) < 1 || len(b64File) < 1 {
  425. email.Error = errors.New("Mail Error: Attach Base64 need have a base64 string and name")
  426. return email
  427. }
  428. email.Error = email.attachB64(b64File, name)
  429. return email
  430. }
  431. // AddInline allows you to add an inline attachment to the email message.
  432. // You can optionally provide a different name for the file.
  433. func (email *Email) AddInline(file string, name ...string) *Email {
  434. if email.Error != nil {
  435. return email
  436. }
  437. if len(name) > 1 {
  438. email.Error = errors.New("Mail Error: Inline can only have a file and an optional name")
  439. return email
  440. }
  441. email.Error = email.attach(file, true, name...)
  442. return email
  443. }
  444. // attach does the low level attaching of the files
  445. func (email *Email) attach(f string, inline bool, name ...string) error {
  446. // Get the file data
  447. data, err := ioutil.ReadFile(f)
  448. if err != nil {
  449. return errors.New("Mail Error: Failed to add file with following error: " + err.Error())
  450. }
  451. // get the file mime type
  452. mimeType := mime.TypeByExtension(filepath.Ext(f))
  453. if mimeType == "" {
  454. mimeType = "application/octet-stream"
  455. }
  456. // get the filename
  457. _, filename := filepath.Split(f)
  458. // if an alternative filename was provided, use that instead
  459. if len(name) == 1 {
  460. filename = name[0]
  461. }
  462. if inline {
  463. email.inlines = append(email.inlines, &file{
  464. filename: filename,
  465. mimeType: mimeType,
  466. data: data,
  467. })
  468. } else {
  469. email.attachments = append(email.attachments, &file{
  470. filename: filename,
  471. mimeType: mimeType,
  472. data: data,
  473. })
  474. }
  475. return nil
  476. }
  477. // attachB64 does the low level attaching of the files but decoding base64 instead have a filepath
  478. func (email *Email) attachB64(b64File string, name string) error {
  479. // decode the string
  480. dec, err := base64.StdEncoding.DecodeString(b64File)
  481. if err != nil {
  482. return errors.New("Mail Error: Failed to decode base64 attachment with following error: " + err.Error())
  483. }
  484. // get the file mime type
  485. mimeType := mime.TypeByExtension(name)
  486. if mimeType == "" {
  487. mimeType = "application/octet-stream"
  488. }
  489. email.attachments = append(email.attachments, &file{
  490. filename: name,
  491. mimeType: mimeType,
  492. data: dec,
  493. })
  494. return nil
  495. }
  496. // getFrom returns the sender of the email, if any
  497. func (email *Email) getFrom() string {
  498. from := email.returnPath
  499. if from == "" {
  500. from = email.sender
  501. if from == "" {
  502. from = email.from
  503. if from == "" {
  504. from = email.replyTo
  505. }
  506. }
  507. }
  508. return from
  509. }
  510. func (email *Email) hasMixedPart() bool {
  511. return (len(email.parts) > 0 && len(email.attachments) > 0) || len(email.attachments) > 1
  512. }
  513. func (email *Email) hasRelatedPart() bool {
  514. return (len(email.parts) > 0 && len(email.inlines) > 0) || len(email.inlines) > 1
  515. }
  516. func (email *Email) hasAlternativePart() bool {
  517. return len(email.parts) > 1
  518. }
  519. // GetMessage builds and returns the email message
  520. func (email *Email) GetMessage() string {
  521. msg := newMessage(email)
  522. if email.hasMixedPart() {
  523. msg.openMultipart("mixed")
  524. }
  525. if email.hasRelatedPart() {
  526. msg.openMultipart("related")
  527. }
  528. if email.hasAlternativePart() {
  529. msg.openMultipart("alternative")
  530. }
  531. for _, part := range email.parts {
  532. msg.addBody(part.contentType, part.body.Bytes())
  533. }
  534. if email.hasAlternativePart() {
  535. msg.closeMultipart()
  536. }
  537. msg.addFiles(email.inlines, true)
  538. if email.hasRelatedPart() {
  539. msg.closeMultipart()
  540. }
  541. msg.addFiles(email.attachments, false)
  542. if email.hasMixedPart() {
  543. msg.closeMultipart()
  544. }
  545. return msg.getHeaders() + msg.body.String()
  546. }
  547. // Send sends the composed email
  548. func (email *Email) Send(client *SMTPClient) error {
  549. if email.Error != nil {
  550. return email.Error
  551. }
  552. if len(email.recipients) < 1 {
  553. return errors.New("Mail Error: No recipient specified")
  554. }
  555. msg := email.GetMessage()
  556. return send(email.from, email.recipients, msg, client)
  557. }
  558. // dial connects to the smtp server with the request encryption type
  559. func dial(host string, port string, encryption encryption, config *tls.Config) (*smtpClient, error) {
  560. var conn net.Conn
  561. var err error
  562. address := host + ":" + port
  563. // do the actual dial
  564. switch encryption {
  565. case EncryptionSSL:
  566. conn, err = tls.Dial("tcp", address, config)
  567. default:
  568. conn, err = net.Dial("tcp", address)
  569. }
  570. if err != nil {
  571. return nil, errors.New("Mail Error on dailing with encryption type " + encryption.string() + ": " + err.Error())
  572. }
  573. c, err := newClient(conn, host)
  574. if err != nil {
  575. return nil, errors.New("Mail Error on smtp dial: " + err.Error())
  576. }
  577. return c, err
  578. }
  579. // smtpConnect connects to the smtp server and starts TLS and passes auth
  580. // if necessary
  581. func smtpConnect(host string, port string, a auth, encryption encryption, config *tls.Config) (*smtpClient, error) {
  582. // connect to the mail server
  583. c, err := dial(host, port, encryption, config)
  584. if err != nil {
  585. return nil, err
  586. }
  587. // send Hello
  588. if err = c.hi("localhost"); err != nil {
  589. c.close()
  590. return nil, errors.New("Mail Error on Hello: " + err.Error())
  591. }
  592. // start TLS if necessary
  593. if encryption == EncryptionTLS {
  594. if ok, _ := c.extension("STARTTLS"); ok {
  595. if config.ServerName == "" {
  596. config = &tls.Config{ServerName: host}
  597. }
  598. if err = c.startTLS(config); err != nil {
  599. c.close()
  600. return nil, errors.New("Mail Error on Start TLS: " + err.Error())
  601. }
  602. }
  603. }
  604. // pass the authentication if necessary
  605. if a != nil {
  606. if ok, _ := c.extension("AUTH"); ok {
  607. if err = c.authenticate(a); err != nil {
  608. c.close()
  609. return nil, errors.New("Mail Error on Auth: " + err.Error())
  610. }
  611. }
  612. }
  613. return c, nil
  614. }
  615. //Connect returns the smtp client
  616. func (server *SMTPServer) Connect() (*SMTPClient, error) {
  617. var a auth
  618. switch server.Authentication {
  619. case AuthPlain:
  620. if server.Username != "" || server.Password != "" {
  621. a = plainAuthfn("", server.Username, server.Password, server.Host)
  622. }
  623. case AuthLogin:
  624. if server.Username != "" || server.Password != "" {
  625. a = loginAuthfn("", server.Username, server.Password, server.Host)
  626. }
  627. case AuthCRAMMD5:
  628. if server.Username != "" || server.Password != "" {
  629. a = cramMD5Authfn(server.Username, server.Password)
  630. }
  631. }
  632. var smtpConnectChannel chan error
  633. var c *smtpClient
  634. var err error
  635. // if there is a ConnectTimeout, setup the channel and do the connect under a goroutine
  636. if server.ConnectTimeout != 0 {
  637. smtpConnectChannel = make(chan error, 2)
  638. go func() {
  639. c, err = smtpConnect(server.Host, fmt.Sprintf("%d", server.Port), a, server.Encryption, new(tls.Config))
  640. // send the result
  641. smtpConnectChannel <- err
  642. }()
  643. }
  644. if server.ConnectTimeout == 0 {
  645. // no ConnectTimeout, just fire the connect
  646. c, err = smtpConnect(server.Host, fmt.Sprintf("%d", server.Port), a, server.Encryption, new(tls.Config))
  647. } else {
  648. // get the connect result or timeout result, which ever happens first
  649. select {
  650. case err = <-smtpConnectChannel:
  651. if err != nil {
  652. return nil, errors.New(err.Error())
  653. }
  654. case <-time.After(server.ConnectTimeout):
  655. return nil, errors.New("Mail Error: SMTP Connection timed out")
  656. }
  657. }
  658. return &SMTPClient{
  659. Client: c,
  660. KeepAlive: server.KeepAlive,
  661. SendTimeout: server.SendTimeout,
  662. }, nil
  663. }
  664. // Reset send RSET command to smtp client
  665. func (smtpClient *SMTPClient) Reset() error {
  666. return smtpClient.Client.reset()
  667. }
  668. // Noop send NOOP command to smtp client
  669. func (smtpClient *SMTPClient) Noop() error {
  670. return smtpClient.Client.noop()
  671. }
  672. // Quit send QUIT command to smtp client
  673. func (smtpClient *SMTPClient) Quit() error {
  674. return smtpClient.Client.quit()
  675. }
  676. // Close closes the connection
  677. func (smtpClient *SMTPClient) Close() error {
  678. return smtpClient.Client.close()
  679. }
  680. // send does the low level sending of the email
  681. func send(from string, to []string, msg string, client *SMTPClient) error {
  682. //Check if client struct is not nil
  683. if client != nil {
  684. //Check if client is not nil
  685. if client.Client != nil {
  686. var smtpSendChannel chan error
  687. // if there is a SendTimeout, setup the channel and do the send under a goroutine
  688. if client.SendTimeout != 0 {
  689. smtpSendChannel = make(chan error, 1)
  690. go func(from string, to []string, msg string, c *smtpClient) {
  691. smtpSendChannel <- sendMailProcess(from, to, msg, c)
  692. }(from, to, msg, client.Client)
  693. }
  694. if client.SendTimeout == 0 {
  695. // no SendTimeout, just fire the sendMailProcess
  696. return sendMailProcess(from, to, msg, client.Client)
  697. }
  698. // get the send result or timeout result, which ever happens first
  699. select {
  700. case sendError := <-smtpSendChannel:
  701. checkKeepAlive(client)
  702. return sendError
  703. case <-time.After(client.SendTimeout):
  704. checkKeepAlive(client)
  705. return errors.New("Mail Error: SMTP Send timed out")
  706. }
  707. }
  708. }
  709. return errors.New("Mail Error: No SMTP Client Provided")
  710. }
  711. func sendMailProcess(from string, to []string, msg string, c *smtpClient) error {
  712. // Set the sender
  713. if err := c.mail(from); err != nil {
  714. return err
  715. }
  716. // Set the recipients
  717. for _, address := range to {
  718. if err := c.rcpt(address); err != nil {
  719. return err
  720. }
  721. }
  722. // Send the data command
  723. w, err := c.data()
  724. if err != nil {
  725. return err
  726. }
  727. // write the message
  728. _, err = fmt.Fprint(w, msg)
  729. if err != nil {
  730. return err
  731. }
  732. err = w.Close()
  733. if err != nil {
  734. return err
  735. }
  736. return nil
  737. }
  738. //check if keepAlive for close or reset
  739. func checkKeepAlive(client *SMTPClient) {
  740. if client.KeepAlive {
  741. client.Client.reset()
  742. } else {
  743. client.Client.quit()
  744. client.Client.close()
  745. }
  746. }