|
1 | 1 | package mail |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bufio" |
4 | 5 | "bytes" |
| 6 | + "context" |
5 | 7 | "errors" |
6 | 8 | "fmt" |
7 | 9 | "io" |
8 | 10 | "math/rand" |
9 | 11 | "mime" |
10 | 12 | "net/mail" |
11 | 13 | "os" |
| 14 | + "os/exec" |
12 | 15 | "path/filepath" |
13 | 16 | "time" |
14 | 17 | ) |
@@ -54,6 +57,9 @@ type Msg struct { |
54 | 57 | embeds []*File |
55 | 58 | } |
56 | 59 |
|
| 60 | +// SendmailPath is the default system path to the sendmail binary |
| 61 | +const SendmailPath = "/usr/bin/sendmail" |
| 62 | + |
57 | 63 | // MsgOption returns a function that can be used for grouping Msg options |
58 | 64 | type MsgOption func(*Msg) |
59 | 65 |
|
@@ -450,6 +456,64 @@ func (m *Msg) appendFile(c []*File, f *File, o ...FileOption) []*File { |
450 | 456 | return append(c, f) |
451 | 457 | } |
452 | 458 |
|
| 459 | +// WriteToSendmail returns WriteToSendmailWithContext with a default timeout of 5 seconds |
| 460 | +func (m *Msg) WriteToSendmail() error { |
| 461 | + tctx, tcfn := context.WithTimeout(context.Background(), time.Second*5) |
| 462 | + defer tcfn() |
| 463 | + return m.WriteToSendmailWithContext(tctx, SendmailPath) |
| 464 | +} |
| 465 | + |
| 466 | +// WriteToSendmailWithContext opens an pipe to the local sendmail binary and tries to send the |
| 467 | +// mail though that. It takes a context.Context, the path to the sendmail binary and additional |
| 468 | +// arguments for the sendmail binary as parameters |
| 469 | +func (m *Msg) WriteToSendmailWithContext(ctx context.Context, sp string, a ...string) error { |
| 470 | + ec := exec.CommandContext(ctx, sp) |
| 471 | + ec.Args = append(ec.Args, "-oi", "-t") |
| 472 | + ec.Args = append(ec.Args, a...) |
| 473 | + |
| 474 | + var ebuf bytes.Buffer |
| 475 | + ec.Stderr = &ebuf |
| 476 | + ses := bufio.NewScanner(&ebuf) |
| 477 | + |
| 478 | + si, err := ec.StdinPipe() |
| 479 | + if err != nil { |
| 480 | + return fmt.Errorf("failed to set STDIN pipe: %w", err) |
| 481 | + } |
| 482 | + |
| 483 | + // Start the execution and write to STDIN |
| 484 | + if err := ec.Start(); err != nil { |
| 485 | + return fmt.Errorf("could not start sendmail execution: %w", err) |
| 486 | + } |
| 487 | + _, err = m.Write(si) |
| 488 | + _, err = si.Write([]byte(".\r\n")) |
| 489 | + if err != nil { |
| 490 | + _ = si.Close() |
| 491 | + return fmt.Errorf("failed to write mail to buffer: %w", err) |
| 492 | + } |
| 493 | + if err := si.Close(); err != nil { |
| 494 | + return fmt.Errorf("failed to close STDIN pipe: %w", err) |
| 495 | + } |
| 496 | + |
| 497 | + // Read the stderr buffer for possible errors |
| 498 | + var serr string |
| 499 | + for ses.Scan() { |
| 500 | + serr += ses.Text() |
| 501 | + } |
| 502 | + if err := ses.Err(); err != nil { |
| 503 | + return fmt.Errorf("failed to read STDERR pipe: %w", err) |
| 504 | + } |
| 505 | + if serr != "" { |
| 506 | + return fmt.Errorf("sendmail execution failed: %s", serr) |
| 507 | + } |
| 508 | + |
| 509 | + // Wait for completion or cancellation of the sendmail executable |
| 510 | + if err := ec.Wait(); err != nil { |
| 511 | + return fmt.Errorf("sendmail command execution failed: %w", err) |
| 512 | + } |
| 513 | + |
| 514 | + return nil |
| 515 | +} |
| 516 | + |
453 | 517 | // encodeString encodes a string based on the configured message encoder and the corresponding |
454 | 518 | // charset for the Msg |
455 | 519 | func (m *Msg) encodeString(s string) string { |
|
0 commit comments