├── demo ├── mail2go-smaller.png ├── body.html ├── config.json └── postfix │ ├── smtp.crt │ ├── Dockerfile │ └── smtp.key ├── go.mod ├── go.sum ├── .gitignore ├── config.go ├── LICENSE ├── usage.go ├── smtp.go ├── README.md └── main.go /demo/mail2go-smaller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KeepSec-Technologies/Mail2Go/HEAD/demo/mail2go-smaller.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/KeepSec-Technologies/Mail2Go 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/wneessen/go-mail v0.4.0 7 | ) 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/wneessen/go-mail v0.4.0 h1:Oo4HLIV8My7G9JuZkoOX6eipXQD+ACvIqURYeIzUc88= 2 | github.com/wneessen/go-mail v0.4.0/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8= 3 | -------------------------------------------------------------------------------- /demo/body.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |Maybe star us on GitHub ;)
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /demo/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "smtp_server": "mail.example.com", 3 | "smtp_port": 587, 4 | "smtp_username": "user@example.com", 5 | "smtp_password": "password_example", 6 | "no_auth": false, 7 | "tls_mode": "tls", 8 | "from_email": "mail2go@example.com" 9 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | mail2go 24 | *.tar.gz 25 | 26 | releases/ 27 | build/ 28 | release-build-go.sh 29 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "os" 6 | ) 7 | 8 | // For config file 9 | type Config struct { 10 | SMTPServer string `json:"smtp_server"` 11 | SMTPPort int `json:"smtp_port"` 12 | SMTPUsername string `json:"smtp_username"` 13 | SMTPPassword string `json:"smtp_password"` 14 | NoAuth bool `json:"no_auth"` 15 | TLSMode string `json:"tls_mode"` 16 | FromEmail string `json:"from_email"` 17 | } 18 | 19 | func loadConfig(filePath string) (Config, error) { 20 | var config Config 21 | 22 | file, err := os.Open(filePath) 23 | if err != nil { 24 | return config, err 25 | } 26 | defer file.Close() 27 | 28 | decoder := json.NewDecoder(file) 29 | err = decoder.Decode(&config) 30 | if err != nil { 31 | return config, err 32 | } 33 | 34 | return config, nil 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 KeepSec Technologies 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /demo/postfix/smtp.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDlTCCAn2gAwIBAgIUahX9BP6n28j7ZDrnVwpBZMXA+J8wDQYJKoZIhvcNAQEL 3 | BQAwWjELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5 4 | MRUwEwYDVQQKDAxPcmdhbml6YXRpb24xFTATBgNVBAMMDG15ZG9tYWluLmNvbTAe 5 | Fw0yNDA1MTQyMjEzNThaFw0yNTA1MTQyMjEzNThaMFoxCzAJBgNVBAYTAlVTMQ4w 6 | DAYDVQQIDAVTdGF0ZTENMAsGA1UEBwwEQ2l0eTEVMBMGA1UECgwMT3JnYW5pemF0 7 | aW9uMRUwEwYDVQQDDAxteWRvbWFpbi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB 8 | DwAwggEKAoIBAQCxZHmz4tCnqZO5h4WMg1eacFbk8FdxrR7VBWx9vX7GoDdcX+OO 9 | UpyLKdFtYqDquqfp2/utwa1XYiVLzbNgFGLsDD3b+dLsJv+Cux9ev52dsfpYUrN8 10 | tinEhpqPR/VT2dAOb1O1RUhi20vyZbQvjFbMF3knef10C6ulEr+H7awIInAqrzSR 11 | +XSSeLSEzH8AW1sGpJUsvZBw+Cz7rCQh0+bLdu5tvb/rUR2a3N4sYPZsByw55dRk 12 | XpggX7rPrAsgBrJnHcj5dLS8HynmaEvqBUnOvPaMwkWGrSoNNABHKVWfzyb574ak 13 | ul/I62c/FzmA+U2LYYpVsBNi6FtlFhzlSm7tAgMBAAGjUzBRMB0GA1UdDgQWBBTZ 14 | xuWPSsBT6oLNEOgnk3P0w1g1LjAfBgNVHSMEGDAWgBTZxuWPSsBT6oLNEOgnk3P0 15 | w1g1LjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQARWfU47YQ7 16 | AyjYos/Aj4OEYLrfTS81aDsJMycmZNZ13D80EC8PB+GvB20wwcdEIOzkSSe6XlX/ 17 | RScwctYaEiAg6E3Xsm7HW5tGRbau0r//OMHEiEGqdUWLLnS+wvqYkdBunJTr1fz4 18 | pS8x4B13DIdQaMIs3oFU+MCu/t2IZmzPHGZMSgKMtxKFaz5QOMTfKWKkNIq82V0P 19 | aB0/X2Q3I0WvRiyN2LWtowFKjrg+23qH0gtClcjd42YLLP+m0c/+2ASMU/H+RMaD 20 | 6JrC+HzVY6IOMnsm/mqa7Xy2fFJ0dhMhXNKpjod4MhjYp29x+LAIxzxnDH38Vo/1 21 | 2U0mVjmBjdBf 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /demo/postfix/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Debian image as a parent image 2 | FROM debian:12 3 | 4 | # Set noninteractive installation to avoid prompts 5 | ENV DEBIAN_FRONTEND=noninteractive 6 | 7 | # Install Postfix 8 | RUN apt-get update && apt-get install -y postfix 9 | 10 | # Copy your TLS certificates into the container 11 | COPY smtp.crt /etc/ssl/certs/smtp.crt 12 | COPY smtp.key /etc/ssl/private/smtp.key 13 | 14 | # Setup Postfix configuration 15 | RUN postconf -e "myhostname = mydomain.com" \ 16 | && postconf -e "smtpd_tls_cert_file = /etc/ssl/certs/smtp.crt" \ 17 | && postconf -e "smtpd_tls_key_file = /etc/ssl/private/smtp.key" \ 18 | && postconf -e "smtpd_tls_security_level = may" \ 19 | && postconf -e "smtpd_tls_auth_only = no" \ 20 | && postconf -e "smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination" \ 21 | && postconf -e "mydestination = $myhostname, localhost.$mydomain, localhost" \ 22 | && postconf -e "mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 0.0.0.0/0" \ 23 | && postconf -e "inet_interfaces = all" \ 24 | && postconf -e "inet_protocols = ipv4" \ 25 | && postconf -e "smtpd_recipient_restrictions = permit_mynetworks permit_sasl_authenticated reject_unauth_destination" \ 26 | && postconf -e "smtp_bind_address = 0.0.0.0" \ 27 | && postconf -e "smtpd_sasl_auth_enable = no" 28 | 29 | # Expose ports for SMTP 30 | EXPOSE 25 31 | 32 | # Start Postfix 33 | CMD ["postfix", "start-fg"] 34 | 35 | -------------------------------------------------------------------------------- /demo/postfix/smtp.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCxZHmz4tCnqZO5 3 | h4WMg1eacFbk8FdxrR7VBWx9vX7GoDdcX+OOUpyLKdFtYqDquqfp2/utwa1XYiVL 4 | zbNgFGLsDD3b+dLsJv+Cux9ev52dsfpYUrN8tinEhpqPR/VT2dAOb1O1RUhi20vy 5 | ZbQvjFbMF3knef10C6ulEr+H7awIInAqrzSR+XSSeLSEzH8AW1sGpJUsvZBw+Cz7 6 | rCQh0+bLdu5tvb/rUR2a3N4sYPZsByw55dRkXpggX7rPrAsgBrJnHcj5dLS8Hynm 7 | aEvqBUnOvPaMwkWGrSoNNABHKVWfzyb574akul/I62c/FzmA+U2LYYpVsBNi6Ftl 8 | FhzlSm7tAgMBAAECggEACKhm72p0u2O7MjhOdbliiPobDTKhwfDGHkJIG91bMHLy 9 | k/XMhs5UL5wx4xnUnfNp08mI/6w1O2kurGpBhMEmw6IkhSj5rDO9dfIpQzGTWsMs 10 | Y17EV3DfAJiyA6fQ1G/Y1LhTdV4IfP96nHEHgBtkXRlEMExztbps45ifp0Qbu/Ke 11 | +Q1OxsF+i3Bu71rFrnV9iOD1crLDlH1ZX686tpAShYwObQwc/nuzLaiICXfPwyI2 12 | 0QJB4Q12o5mrfKhAUzePZEiwLdRA8NOA4lRbTH9OjYoSa9tN63czxrH18BoeSed0 13 | n85t2UY92qjDCS3RdjjvUAMAU5s6LmaXz3BWDLvfDQKBgQDxzsIzwxF2ZjVn5ECQ 14 | W0yHyKPPxCbeQeMzFmkQ9pweK0iVooTmR5crr2s0LylMxKX8qMWbfI9ZoVOZYqU2 15 | JipmV23WlCw246tbkSeaQqlvNQzdx2hmJ/lDiJVSUWVAYkoZMZisPMIPZCtuWM3W 16 | Sv6e0fYCDuFkpZKP8sii+WDrbwKBgQC7zdtw8r2Akj7JlCokfWUSR75pdbKKvQWO 17 | a/4iBiF+xJEUiBwM3LxbbNoR8Eu/w43JCksAgQh+bFOUL5jE6l/8bEPxTDrGp6zt 18 | GIjuaXmrwpmLyXRuFzzaYH9BUMLiGJyvl6hfVgTWVQODoxGl3Fs1v2XGVcspeqLR 19 | zHlwvnFNYwKBgQC9TdvVwDJsmenXkudE3GUWrGoqXur692QSe8n3YMmqCMLDer9G 20 | tOdRaPyplv5jPlSgb9R8PNDRH66eF481zD1Hb8zqv4e51RUzE3mImAWjrUmMWu+N 21 | gl/vkf8sudJlzE0sWhqnRM28VPR3aAiFaqLZ3ZAV3mZwb/tEvJJ8nHVW+wKBgFAC 22 | E0q7HyB3LWiTRqDlCvoOtoAXNEkG3iceutWj5wEqnOQyWEDiiRwp831Q8fRwSycq 23 | y2kbj5LMc47d+Cdr8hiHxyo1X5TiOjmICk4HgV47OU7kNEXygGpIUe5xiZTpB1eH 24 | NKPo8YaeETEzd4FBr1nmgGVOh47UQClBAzuU5pAhAoGBAOqVG+Gkx4ecOASXlG+r 25 | nyBVKvFqfOOHlNViZ622IC9nBB7bSJ3QAnDeNMThC1/wvMlnziFLfaWnu3ciiKNq 26 | VS+MNccVscIn3aOzKJTdvzXPaLPXd7XJZT4BUpBDNAeBmnfxMNdQbj7lJQTrVFm9 27 | MhKbE3mdcIDspS7gH+TFPy3R 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /usage.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | func Usage() { 9 | fmt.Fprintf(os.Stderr, "Usage: %s [options]\n", os.Args[0]) 10 | fmt.Fprintln(os.Stderr, "") 11 | fmt.Fprintln(os.Stderr, " -s, --smtp-server SMTP server for sending emails") 12 | fmt.Fprintln(os.Stderr, " -p, --smtp-port SMTP server port (Default: 587)") 13 | fmt.Fprintln(os.Stderr, " -u, --smtp-username Username for SMTP authentication") 14 | fmt.Fprintln(os.Stderr, " -w, --smtp-password Password for SMTP authentication") 15 | fmt.Fprintln(os.Stderr, " -na, --no-auth No authentication required") 16 | fmt.Fprintln(os.Stderr, " -l, --tls-mode TLS mode (none, tls-skip, tls)") 17 | fmt.Fprintln(os.Stderr, " -f, --from-email Email address to send from") 18 | fmt.Fprintln(os.Stderr, "") 19 | fmt.Fprintln(os.Stderr, " -c, --config Path to the SMTP json config file which replaces the above arguments") 20 | fmt.Fprintln(os.Stderr, "") 21 | fmt.Fprintln(os.Stderr, " -t, --to-email Email addresses that will receive the email, comma-separated") 22 | fmt.Fprintln(os.Stderr, " -r, --reply-to Email address to reply to (optional)") 23 | fmt.Fprintln(os.Stderr, " -h, --subject Subject of the email") 24 | fmt.Fprintln(os.Stderr, " -b, --body Body of the email") 25 | fmt.Fprintln(os.Stderr, " -af, --attachments File paths for attachments, comma-separated (optional)") 26 | fmt.Fprintln(os.Stderr, " -bf, --body-file File path for HTML email body (replaces the --body argument)") 27 | fmt.Fprintln(os.Stderr, " -v, --version Application version") 28 | fmt.Fprintln(os.Stderr, "") 29 | fmt.Fprintln(os.Stderr, " Ensure all required flags are provided.") 30 | os.Exit(1) 31 | } 32 | -------------------------------------------------------------------------------- /smtp.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/wneessen/go-mail" 9 | ) 10 | 11 | // sendEmail function modified to include the reply-to header 12 | func sendEmail(smtpServer string, smtpPort int, username string, password string, from string, to []string, replyTo string, subject, body, bodyFile string, attachments []string, tlsMode string, noAuth bool) error { 13 | // Create a new message 14 | m := mail.NewMsg() 15 | if err := m.From(from); err != nil { 16 | return err 17 | } 18 | 19 | // Set recipient(s) 20 | if err := m.To(to...); err != nil { 21 | return err 22 | } 23 | 24 | // Set the subject 25 | m.Subject(subject) 26 | 27 | // Set the body 28 | if bodyFile != "" { 29 | m.SetBodyString(mail.TypeTextHTML, body) 30 | } else { 31 | m.SetBodyString(mail.TypeTextPlain, body) 32 | } 33 | 34 | // Add attachments 35 | for _, attachment := range attachments { 36 | m.AttachFile(attachment) 37 | } 38 | 39 | // Add the reply-to header if provided 40 | if replyTo != "" { 41 | if err := m.ReplyTo(replyTo); err != nil { 42 | return err 43 | } 44 | } 45 | 46 | clientOptions := []mail.Option{ 47 | mail.WithPort(smtpPort), 48 | } 49 | 50 | // Define client options 51 | if !noAuth && (username != "" || password != "") { 52 | clientOptions = append( 53 | clientOptions, 54 | mail.WithSMTPAuth(mail.SMTPAuthLogin), 55 | mail.WithUsername(username), 56 | mail.WithPassword(password), 57 | ) 58 | } 59 | 60 | // Conditionally add TLS options based on tlsMode 61 | switch tlsMode { 62 | case "none": 63 | clientOptions = append(clientOptions, mail.WithTLSPolicy(mail.NoTLS)) 64 | case "tls-skip": 65 | tlsSkipConfig := &tls.Config{ 66 | InsecureSkipVerify: true, 67 | ServerName: smtpServer, 68 | } 69 | clientOptions = append(clientOptions, mail.WithTLSConfig(tlsSkipConfig)) 70 | case "tls": 71 | clientOptions = append(clientOptions, mail.WithTLSPolicy(mail.TLSMandatory)) 72 | } 73 | 74 | // Create a new client using the options 75 | c, err := mail.NewClient(smtpServer, clientOptions...) 76 | if err != nil { 77 | fmt.Printf("Failed to create SMTP client: %v", err) 78 | } 79 | if c == nil { 80 | fmt.Printf("SMTP client is nil") 81 | } 82 | 83 | // Send the email 84 | if err := c.DialAndSend(m); err != nil { 85 | fmt.Printf("Error sending email: %v", err) 86 | return err 87 | } 88 | 89 | fmt.Printf("\nEmail sent successfully to %s from %s\n", strings.Join(to, ", "), from) 90 | return nil 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
4 |
5 | # Mail2Go - Lightweight CLI SMTP client
6 |
7 | [](./LICENSE)
8 | [](https://github.com/KeepSec-Technologies/Mail2Go/issues)
9 | [](./go.mod)
10 |
11 | Mail2Go is a very lightweight command-line SMTP client written in Go, designed to send emails from the command-line easily.
12 |
13 | ## Table of Contents
14 |
15 | - [Features](#features)
16 | - [Requirements](#requirements)
17 | - [Installation](#installation)
18 | - [Building from Source](#building-from-source)
19 | - [Usage](#usage)
20 | - [Examples](#examples)
21 | - [Contributing](#contributing)
22 | - [License](#license)
23 |
24 | ## Features
25 |
26 | - **Send Emails with Ease**: Quickly send emails with subject, body, and multiple recipients.
27 | - **Attachments Support**: Attach multiple files of various types to your emails.
28 | - **HTML and Plain Text**: Supports both HTML and plain text formats for email bodies.
29 | - **Command Line Interface**: Easy-to-use CLI arguments for configuring and sending emails.
30 | - **Flexible Configuration**: SMTP server, TLS, port, username, and password can be configured through CLI arguments or a JSON configuration file.
31 | - **Automatic Configuration File Detection**: Will automatically search for a default configuration file in the user's config directory if no configuration file is provided (e.g., `~/.config/mail2go/config.json`).
32 |
33 | ## Requirements
34 |
35 | - Go 1.22 or higher recommended (for build).
36 | - Access to an SMTP server for sending emails.
37 |
38 | ## Installation
39 |
40 | 1. Download the Linux amd64 binary with wget (more versions on [release](https://github.com/KeepSec-Technologies/Mail2Go/releases/tag/1.1.8) tab):
41 |
42 | ```shell
43 | wget https://github.com/KeepSec-Technologies/Mail2Go/releases/download/1.1.8/mail2go_linux_amd64_1.1.8.tar.gz
44 | ```
45 |
46 | 2. Unpack it with tar
47 |
48 | ```shell
49 | tar -xf mail2go_linux_amd64_1.1.8.tar.gz
50 | ```
51 |
52 | 3. Move it to your /usr/local/bin/ (Optional):
53 |
54 | ```shell
55 | sudo mv mail2go /usr/local/bin/mail2go
56 | ```
57 |
58 | ## Building from Source
59 |
60 | 1. Ensure you have Go installed on your system. You can download Go from [here](https://go.dev/dl/).
61 | 2. Clone the repository:
62 |
63 | ```shell
64 | git clone https://github.com/KeepSec-Technologies/Mail2Go
65 | ```
66 |
67 | 3. Navigate to the cloned directory:
68 |
69 | ```shell
70 | cd Mail2Go
71 | ```
72 |
73 | 4. Build the tool:
74 |
75 | ```shell
76 | CGO_ENABLED=0 go build -a -installsuffix cgo -o mail2go .
77 | ```
78 |
79 | ## Usage
80 |
81 | Run the Mail2Go tool with the required arguments:
82 |
83 | ```text
84 | -s, --smtp-server SMTP server for sending emails
85 | -p, --smtp-port SMTP server port (Default: 587)
86 | -u, --smtp-username Username for SMTP authentication
87 | -w, --smtp-password Password for SMTP authentication
88 | -na, --no-auth No authentication required
89 | -l, --tls-mode TLS mode (none, tls-skip, tls)
90 | -f, --from-email Email address to send from
91 |
92 | -c, --config Path to the SMTP json config file which replaces the above arguments
93 |
94 | -t, --to-email Email addresses that will receive the email, comma-separated
95 | -r, --reply-to Email address to reply to (optional)
96 | -h, --subject Subject of the email
97 | -b, --body Body of the email
98 | -af, --attachments File paths for attachments, comma-separated (optional)
99 | -bf, --body-file File path for HTML email body (replaces the --body argument)
100 | -v, --version Application version
101 | ```
102 |
103 | ## Examples
104 |
105 | ```shell
106 | # Basic example:
107 | mail2go -s mail.example.com -p 587 -u user@example.com -w password123 -l tls -f mail2go@example.com -t admin@example.com -h 'Test Mail2Go Subject' -b 'This is a body!'
108 |
109 | # Example with two recipients, the body from an HTML file and two attached files (can be more):
110 | mail2go -s mail.example.com -p 587 -u user@example.com -w password123 -l tls -f mail2go@example.com -t admin@example.com,other@example.com -h 'Test Mail2Go Subject' -bf demo/body.html -af README.md,demo/mail2go-smaller.png
111 |
112 | # Example with a reply-to address:
113 | mail2go -s mail.example.com -p 587 -u user@example.com -w password123 -l tls -f mail2go@example.com -t admin@example.com -r replytome@example.com -h 'Test Mail2Go Subject' -b 'This is a body!'
114 |
115 | # Example without authentication and no TLS:
116 | mail2go -s mail.example.com -p 25 -l none -f mail2go@example.com -t admin@example.com -h 'Test Mail2Go Subject' -b 'This is a body!'
117 |
118 | # Example of a local SMTP server with a TLS certificate that can't be verified:
119 | mail2go -s localhost -p 587 -u user@example.com -w password123 -l tls-skip -f mail2go@example.com -t admin@example.com -h 'Test Mail2Go Subject' -b 'This is a body!'
120 | ```
121 |
122 | Example of json configuration file to pass to Mail2Go:
123 |
124 | ```json
125 | {
126 | "smtp_server": "mail.example.com",
127 | "smtp_port": 587,
128 | "smtp_username": "user@example.com",
129 | "smtp_password": "password_example",
130 | "no_auth": false,
131 | "tls_mode": "tls",
132 | "from_email": "mail2go@example.com"
133 | }
134 | ```
135 |
136 | Example of the command that goes with it:
137 |
138 | ```shell
139 | mail2go -c demo/config.json -t admin@example.com -h 'Test Mail2Go Subject' -b 'This is a body!'
140 | ```
141 |
142 | If you are using the default configuration file at `~/.config/mail2go/config.json` it would look like this:
143 |
144 | ```shell
145 | mail2go -t admin@example.com -h 'Test Mail2Go Subject' -b 'This is a body!'
146 | ```
147 |
148 | **Note:** CLI arguments will override configuration files (from the `--config` argument or the default location at `~/.config/mail2go/config.json`).
149 |
150 | ## Contributing
151 |
152 | Contributions to Mail2Go are welcome. Please fork the repository and submit a pull request with your changes or improvements.
153 |
154 | ## License
155 |
156 | This project is licensed under MIT - see the LICENSE file for details.
157 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "os"
7 | "path/filepath"
8 | "strings"
9 | )
10 |
11 | var version = "1.1.8"
12 |
13 | var (
14 | // Long-form flags
15 | smtpServer string
16 | smtpPort int
17 | username string
18 | password string
19 |
20 | tlsMode string
21 |
22 | configFile string
23 |
24 | fromEmail string
25 | toEmail string
26 | replyTo string
27 |
28 | subject string
29 | body string
30 |
31 | attachmentsFiles string
32 | bodyFile string
33 |
34 | showVersion bool
35 |
36 | // Short-form flags
37 | smtpServerShort string
38 | smtpPortShort int
39 | usernameShort string
40 | passwordShort string
41 |
42 | noAuth bool
43 | noAuthShort bool
44 |
45 | tlsModeShort string
46 |
47 | configFileShort string
48 |
49 | fromEmailShort string
50 | toEmailShort string
51 | replyToShort string
52 |
53 | subjectShort string
54 | bodyShort string
55 |
56 | attachmentsFilesShort string
57 | bodyFileShort string
58 |
59 | showVersionShort bool
60 | )
61 |
62 | func init() {
63 | // Long-form flags
64 | flag.StringVar(&smtpServer, "smtp-server", "", "SMTP server for sending emails")
65 | flag.IntVar(&smtpPort, "smtp-port", 587, "SMTP server port")
66 | flag.StringVar(&username, "smtp-username", "", "Username for SMTP authentication")
67 | flag.StringVar(&password, "smtp-password", "", "Password for SMTP authentication")
68 | flag.BoolVar(&noAuth, "no-auth", false, "Use unauthenticated SMTP")
69 |
70 | flag.StringVar(&tlsMode, "tls-mode", "tls", "TLS mode (none, tls-skip, tls)")
71 |
72 | flag.StringVar(&configFile, "config", "", "Path to the SMTP config file")
73 |
74 | flag.StringVar(&fromEmail, "from-email", "", "Email address to send from")
75 | flag.StringVar(&toEmail, "to-email", "", "Email addresses that will receive the email, comma-separated")
76 | flag.StringVar(&replyTo, "reply-to", "", "Email address to reply to")
77 |
78 | flag.StringVar(&subject, "subject", "", "Subject of the email")
79 | flag.StringVar(&body, "body", "", "Body of the email")
80 |
81 | flag.StringVar(&attachmentsFiles, "attachments", "", "File paths for attachments, comma-separated")
82 | flag.StringVar(&bodyFile, "body-file", "", "File path for email body")
83 |
84 | flag.BoolVar(&showVersion, "version", false, "Display application version")
85 |
86 | // Short-form flags
87 | flag.StringVar(&smtpServerShort, "s", "", "SMTP server for sending emails (short)")
88 | flag.IntVar(&smtpPortShort, "p", 587, "SMTP server port (short)")
89 | flag.StringVar(&usernameShort, "u", "", "Username for SMTP authentication (short)")
90 | flag.StringVar(&passwordShort, "w", "", "Password for SMTP authentication (short)")
91 | flag.BoolVar(&noAuthShort, "na", false, "Use unauthenticated SMTP (short)")
92 |
93 | flag.StringVar(&tlsModeShort, "l", "tls", "TLS mode (short)")
94 |
95 | flag.StringVar(&configFileShort, "c", "", "Path to the SMTP config file (short)")
96 |
97 | flag.StringVar(&fromEmailShort, "f", "", "Email address to send from (short)")
98 | flag.StringVar(&toEmailShort, "t", "", "Email addresses that will receive the email, comma-separated (short)")
99 | flag.StringVar(&replyToShort, "r", "", "Email address to reply to (short)")
100 |
101 | flag.StringVar(&subjectShort, "h", "", "Subject of the email (short)")
102 | flag.StringVar(&bodyShort, "b", "", "Body of the email (short)")
103 |
104 | flag.StringVar(&attachmentsFilesShort, "af", "", "File paths for attachments, comma-separated (short)")
105 | flag.StringVar(&bodyFileShort, "bf", "", "File path for email body (short)")
106 |
107 | flag.BoolVar(&showVersionShort, "v", false, "Display application version")
108 | }
109 |
110 | func main() {
111 | // Override the default flag.Usage
112 | flag.Usage = Usage
113 | flag.Parse()
114 |
115 | showVersion = showVersion || showVersionShort
116 | if showVersion {
117 | fmt.Printf("Mail2Go Version: %s\n", version)
118 | os.Exit(0)
119 | }
120 |
121 | var config Config = Config{}
122 |
123 | // Load config file
124 | configFile = priorityString([]string{configFile, configFileShort})
125 | if configFile == "" {
126 | // config file not provided -- look for default file
127 | if path, err := os.UserConfigDir(); err == nil {
128 | path = filepath.Join(path, "mail2go", "config.json")
129 | if _, err := os.Stat(path); err == nil {
130 | configFile = path
131 | }
132 | }
133 | }
134 | if configFile != "" {
135 | c, err := loadConfig(configFile)
136 | if err != nil {
137 | fmt.Printf("Error loading config file: %v", err)
138 | }
139 | config = c
140 | }
141 |
142 | // Clearly define our config priorities, lowest to highest: config files, long flags, short flags
143 | smtpServer = priorityString([]string{config.SMTPServer, smtpServer, smtpServerShort})
144 | smtpPort = priorityInt(587, []int{config.SMTPPort, smtpPort, smtpPortShort})
145 | username = priorityString([]string{config.SMTPUsername, username, usernameShort})
146 | password = priorityString([]string{config.SMTPPassword, password, passwordShort})
147 | noAuth = config.NoAuth || noAuth || noAuthShort
148 | tlsMode = priorityString([]string{config.TLSMode, tlsMode, tlsModeShort})
149 | fromEmail = priorityString([]string{config.FromEmail, fromEmail, fromEmailShort})
150 |
151 | toEmail = priorityString([]string{toEmail, toEmailShort})
152 | replyTo = priorityString([]string{replyTo, replyToShort})
153 | subject = priorityString([]string{subject, subjectShort})
154 | body = priorityString([]string{body, bodyShort})
155 | attachmentsFiles = priorityString([]string{attachmentsFiles, attachmentsFilesShort})
156 | bodyFile = priorityString([]string{bodyFile, bodyFileShort})
157 |
158 | // Check if required flags or config values are missing
159 | if smtpServer == "" || fromEmail == "" || toEmail == "" || subject == "" {
160 | fmt.Fprintln(os.Stderr, "Error: Required flags or config values are missing.")
161 | Usage()
162 | }
163 |
164 | // Check if either direct input or file path is provided for body
165 | if body == "" && bodyFile == "" {
166 | fmt.Fprintln(os.Stderr, "Error: Subject and body are required, either directly or through a specified file.")
167 | Usage()
168 | }
169 |
170 | // Read body from files if provided
171 | if bodyFile != "" {
172 | content, err := os.ReadFile(bodyFile)
173 | if err != nil {
174 | fmt.Printf("\nError reading body file: %v\n", err)
175 | }
176 | body = priorityString([]string{string(content), body}) //preserve the "flags override files" semantic
177 | }
178 |
179 | // Split attachment file paths
180 | var attachmentPaths []string
181 | if attachmentsFiles != "" {
182 | attachmentPaths = strings.Split(attachmentsFiles, ",")
183 | }
184 |
185 | // Split recipient email addresses
186 | var toEmails []string
187 | if toEmail != "" {
188 | toEmails = strings.Split(toEmail, ",")
189 | }
190 |
191 | if len(toEmails) == 0 {
192 | fmt.Fprintln(os.Stderr, "Error: At least one recipient email address is required.")
193 | Usage()
194 | }
195 |
196 | sendEmail(smtpServer, smtpPort, username, password, fromEmail, toEmails, replyTo, subject, body, bodyFile, attachmentPaths, tlsMode, noAuth)
197 | }
198 |
199 | func priorityString(strings []string) string {
200 | var result = ""
201 | for _, val := range strings {
202 | if val != "" {
203 | result = val
204 | }
205 | }
206 | return result
207 | }
208 |
209 | func priorityInt(emptyval int, ints []int) int {
210 | var result = emptyval
211 | for _, val := range ints {
212 | if val != emptyval {
213 | result = val
214 | }
215 | }
216 | return result
217 | }
218 |
--------------------------------------------------------------------------------