├── .gitignore ├── LICENSE ├── README.md ├── config.go ├── demo ├── body.html ├── config.json ├── mail2go-smaller.png └── postfix │ ├── Dockerfile │ ├── smtp.crt │ └── smtp.key ├── go.mod ├── go.sum ├── main.go ├── smtp.go └── usage.go /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 4 | 5 | # Mail2Go - Lightweight CLI SMTP client 6 | 7 | [![License](https://img.shields.io/github/license/KeepSec-Technologies/Mail2Go)](./LICENSE) 8 | [![GitHub issues](https://img.shields.io/github/issues-raw/KeepSec-Technologies/Mail2Go)](https://github.com/KeepSec-Technologies/Mail2Go/issues) 9 | [![GitHub go.mod Go version (branch & subdirectory of monorepo)](https://img.shields.io/github/go-mod/go-version/KeepSec-Technologies/Mail2Go/main)](./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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /demo/body.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Mail2Go is awesome! 6 | 7 | 8 | 9 | 10 |

Mail2Go is awesome!

11 |

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 | } -------------------------------------------------------------------------------- /demo/mail2go-smaller.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KeepSec-Technologies/Mail2Go/2cbd38d31dd549080afa9dafbd5dd0e09cea700f/demo/mail2go-smaller.png -------------------------------------------------------------------------------- /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.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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------