39

I am writing a Bash shell script for Mac that sends an email notification by opening an automator application that sends email out with the default mail account in Mail.app. The automator application also attaches a text file that the script has written to. The problems with this solution are

  1. It is visible in the GUI when sending
  2. It steals focus if Mail is not the current application
  3. It is dependent on Mail.app's account setup being valid in the future

I figure to get around those shortcomings I should send the mail directly from the script by entering SMTP settings, address to send to, etc. directly in the script. The catch is I would like to deploy this script on multiple computers (10.5 and 10.6) without enabling Postfix on the computer. Is it possible to do this in the script so it will run on a base Mac OS X install of 10.5. and 10.6?

Update: I've found the -bs option for Sendmail which seems to be what I need, but I'm at a loss of how to specify settings.

Also, to clarify, the reason I'd like to specify SMTP settings is that mails from localhost on port 25 sent out via Postfix would be blocked by most corporate firewalls, but if I specify the server and an alternate port I won't run into that problem.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ridogi
  • 451
  • 1
  • 5
  • 9

12 Answers12

56

Previously, this answer was based on the default inclusion of a recent Python on Mac OS X. Since then, the Python ecosystem has evolved and Python is not available on a clean install. This answer has been updated for modern systems, but is much more involved and exceeds the scope of the original poster's request.

Python and it's built-in standard library provides some nice facilities for sending email if you're willing to install it. Consider using the stock installer or installing homebrew followed by brew install python.

From there, customize the following script based on stock examples to suit your needs.

# Settings

SMTP_SERVER = 'mail.myisp.com'
SMTP_PORT = 25
SMTP_USERNAME = 'myusername'
SMTP_PASSWORD = '$uper$ecret'
SMTP_FROM = 'sender@example.com'
SMTP_TO = 'recipient@example.com'

TEXT_FILENAME = '/script/output/my_attachment.txt'
MESSAGE = """This is the message
to be sent to the client.
"""

# Now construct the message
import pathlib
import smtplib
import email.message

msg = email.message.EmailMessage()
msg.set_content(MESSAGE)
text_path = pathlib.Path(TEXT_FILENAME)
msg.add_attachment(
    text_path.read_text(),
    maintype='text',
    subtype='plain',
    filename=text_path.name,
)
msg['From'] = SMTP_FROM
msg['To'] = SMTP_TO
# msg['Subject'] = SMTP_SUBJECT

# Now send the message
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as mailer:
    mailer.login(SMTP_USERNAME, SMTP_PASSWORD)
    mailer.send_message(msg)

I hope this helps.

Jason R. Coombs
  • 41,115
  • 10
  • 83
  • 93
  • Darn! I wanted to suggest the very same just the other moment. +1 anyways. – Boldewyn Dec 21 '09 at 17:06
  • This is a great idea. I'm just now starting to learn python, so once I know my way around the language this may be just what I'm looking for. – ridogi Dec 21 '09 at 18:16
  • 1
    I just tested this and it helped me greatly. It did not work at first and to get it to wotk i had to (dont quite understand why) remove the parameters from this line `mailer = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)` and pass them instead on the "connect" call. Can anyone confirm/explain that? (Python 2.7.2, OS x 10.8) – KillerX Aug 17 '12 at 07:41
  • 2
    @KillerX Thanks for pointing this out. The usage was incorrect. In reading the [docs](http://docs.python.org/library/smtplib.html), I now see that `connect` is called automatically if host/port are passed to the SMTP constructor. The second call to connect merely attempts to reconnect to 'localhost:25', which is probably not correct. I've updated the example to omit the call to connect (but left it as a comment for posterity). Your approach of only providing the host/port to the connect method is also valid. Thanks for the feedback. – Jason R. Coombs Aug 19 '12 at 17:56
  • Great script! Any ideas how to include a subject with this? – BSUK Feb 11 '16 at 17:30
  • 2
    @BSUK: After `add_header('To', ...)`, `msg.add_header('Subject', SUBJECT)` where `SUBJECT` is a string literal or variable containing the string of the subject. – Jason R. Coombs Feb 15 '16 at 16:21
  • This was very helpful. You can also add `mailer.set_debuglevel(1)` before `mailer.sendmail` to get some debug information out – blueether Aug 23 '19 at 16:18
  • In my Python 3.7 install on Mac I was getting errors like `Module has no attribute "MIMEMultipart"` so I had to replace `msg = email.MIMEMultipart.MIMEMultipart()` with the simpler `msg = MIMEMultipart()` and add the import `from email.mime.multipart import MIMEMultipart`. Same with `body = MIMEText(MESSAGE)` and `attachment = MIMEBase('text', 'plain')` with the corresponding extra imports `from email.mime.text import MIMEText` and `from email.mime.base import MIMEBase`. Also if using TLS auth e.g. Amazon SES then replace `mailer = smtplib.SMTP(...` with `mailer = smtplib.SMTP_SSL(...`. – abulka Apr 13 '20 at 05:11
  • 1
    The Internet is full of stale examples of how to send email in Python. With Python 3.6+ you will want to use the modern `EmailMessage` API instead of the legacy `MIMEMultipart` etc jumble. The [examples in the `email` module documentation](https://docs.python.org/3/library/email.examples.html) is a good starting point. See also the [`smtplib`](https://docs.python.org/3/library/smtplib.html) documentation for the sending part. What exactly works (`SMTP` with `startts` on port 587, or `SMTP_SSL` on port 465, or something else entirely) depends on the individual server you are trying to use. – tripleee Jun 13 '23 at 12:31
  • 1
    Thanks @tripleee for the nudge. I've refreshed the answer to match more modern expectations. – Jason R. Coombs Jun 24 '23 at 15:50
41

Actually, "mail" works just as well.

mail -s "subject line" name@address.ext < filename

works perfectly fine, as long as you have SMTP set up on your machine. I think that most Macs do, by default.

If you don't have SMTP, then the only thing you're going to be able to do is go through Mail.app. An ALTERNATIVE way to go through mail.app is via AppleScript. When you tell Mail.app to send mail via AppleScript you can tell it to not pop up any windows... (this does still require Mail.app to be configured).

Introduction to Scripting Mail has a good description of how to work with mail in AppleScript.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Brian Postow
  • 11,709
  • 17
  • 81
  • 125
  • I am currently using postfix and my script contains the line cat /pathto/file.txt | mail -s "subject" address@example.com. I realize I could edit Postfix to specify an SMTP server with http://www.macosxhints.com/article.php?story=20081217161612647 but I am interested in putting the settings in the bash script itself. The applescript idea is a good one. I like that Mail.app wouldn't steal focus from the user (as it does with my automator workflow), but it still does require Mail.app be set up correctly. If I can't get these settings into my script, applescript may be the best alternative. – ridogi Dec 21 '09 at 18:25
  • what smtp settings are you trying to work with? you can probably set them, possibly with shell variables... – Brian Postow Dec 21 '09 at 19:38
  • It doesn't matter which server. Gmail, fastmail, Kerio, Exchange, etc. I would imagine any solution would work for any of them, but if there was a solution that worked with only one provider that would still be helpful. Specifying an alternate port would also be helpful as I mentioned in the update to my question above. – ridogi Dec 21 '09 at 19:45
  • 1
    Note that you press Control-D to send. – incandescentman Feb 28 '14 at 01:59
  • 1
    This is the easiest way to do it. It just works. Much better than writing a python script ;-) – Jeff Whiting Apr 01 '15 at 15:47
  • I tried `mail -s "subject line" name@address.ext "hi"` on Mac and it just wait for something. What am I doing wrong? – Anton Tarasenko Apr 28 '15 at 12:36
  • The unix command `mail` works fine , but only if the server accept SMTP, I guess. I had to use my iCloud To email address, it would not work with our in-house server. – Gab Jan 07 '20 at 18:46
  • `mail` by itself will simply connect to your local Sendmail / Postfix, so this is not really an answer to the question. If you _have_ configured a local MTA to send over Gmail or etc, then of course this (just like using your Mail.app or Thunderbird etc) will use that server for outgoing email submissions; but the OP wanted to avoid that. – tripleee Jun 13 '23 at 06:57
15

There is a program called Sendmail.

You probably don't want to use the -bs command unless you are sending it as raw SMTP like Martin's example. -bs is for running an SMTP server as a deamon. Sendmail will send directly to the receiving mail server (on port 25) unless you override it in the configuration file. You can specify the configuration file by the -C paramter.

In the configuration, you can specify a relay server (any mail server or sendmail running -bs on another machine)

Using a properly configured relay server is good idea because when IT manages mail servers they implement SPF and domain keys. That keeps your mail out of the junk bin.

If port 25 is blocked you are left with two options.

  1. Use the corporate SMTP server.
  2. Run sendmail -bd on a machine outside of the corporate firewall that listens on a port other than 25.

I believe you can add configuration parameters on the command line. What you want is the SMART_HOST option. So call Sendmail like sendmail -OSMART_HOST=nameofhost.com.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Byron Whitlock
  • 52,691
  • 28
  • 123
  • 168
  • 3
    hmm why the downvotes? sendmail -tbyron@address.com \nhellow byron\n. works just fine for me! – Byron Whitlock Nov 30 '09 at 21:33
  • 3
    because you could hardly have put any less effort into your answer. –  Nov 30 '09 at 21:44
  • 7
    If the answer is good enough, I don't see why there should be more effort. More is not always better. – Fortega Dec 21 '09 at 15:02
  • 10
    votes should reflect relevance of an answer, not efforts put in it. – mouviciel Dec 21 '09 at 15:02
  • Can the -bs option in sendmail do what I am trying to do? – ridogi Dec 21 '09 at 19:15
  • I'm fine with the option of specifying a local SMTP server, but the question of how to specify that in a bash script is what I'm trying to get at. – ridogi Dec 22 '09 at 05:04
  • If you're going to use your own SMTP server (such as you would be if you ran sendmail), you may have some difficulty originating/relaying messages, depending on the host IP. Before you consider this option, I'd check http://www.spamhaus.org/ to make sure your IP address isn't listed in a common DNS blocklist. – Jason R. Coombs Dec 22 '09 at 13:43
10

Probably the only way you could do this, while keeping the program self-sufficient, is if you have direct access to an SMTP server from the clients.

If you do have direct access to an SMTP server you can use the SMTP example from wikipedia and turn it into something like this:

#!/bin/bash
telnet smtp.example.org 25 <<_EOF
HELO relay.example.org
MAIL FROM:<joe@example.org>
RCPT TO:<jane@example.org>
DATA
From: Joe <joe@example.org>
To: Jane <jane@example.org>
Subject: Hello

Hello, world!
.
QUIT
_EOF

To handle errors I would redirect the output from telnet to a file and then grep that for a "success message" later. I am not sure what format the message should be, but I see something like "250 2.0.0 Ok: queued as D86A226C574" in the output from my SMTP server. This would make me grep for "^250.*queued as".

Martin Olsen
  • 1,895
  • 1
  • 17
  • 20
  • -1 That is a nice example, but useless in a real environment. For one: Where is the error-handling? – Tomas Dec 21 '09 at 15:02
  • Shouldn't there be an empty line between the headers and the content in the DATA section? – Boldewyn Dec 21 '09 at 17:08
  • 3
    @Tomas: Answers in StackOverflow are mostly not adapted for industry strength robustness. And as proof of concept this is really nice. – Boldewyn Dec 21 '09 at 17:09
  • Technically, telnet is an external program and therefore this is not self -sufficient :) However, +1 for SMTP example. – Kimvais Dec 21 '09 at 17:30
  • @Kimvais: Neither is grep, bash, etc... ;-) Also, I can find no indication of telnet not being part of the default installation. – Martin Olsen Dec 21 '09 at 18:15
7

I whipped this up for the challenge. If you remove the call to 'dig' to obtain the mail relay, it is a 100% native Bash script.

#!/bin/bash
MAIL_FROM="sfinktah@bash.spamtrak.org"
RCPT_TO="sfinktah@bash.spamtrak.org"
MESSAGE=message.txt
SMTP_PORT=25
SMTP_DOMAIN=${RCPT_TO##*@}

index=1
while read PRIORITY RELAY
do
    RELAY[$index]=$RELAY
    ((index++))
done < <( dig +short MX $SMTP_DOMAIN )

RELAY_COUNT=${#RELAY[@]}
SMTP_COMMANDS=( "HELO $HOSTNAME" "MAIL FROM: <$MAIL_FROM>" "RCPT TO: <$RCPT_TO>" "DATA" "." "QUIT" )
SMTP_REPLY=([25]=OK [50]=FAIL [51]=FAIL [52]=FAIL [53]=FAIL [54]=FAIL [55]=FAIL [45]=WAIT [35]=DATA [22]=SENT)

for (( i = 1 ; i < RELAY_COUNT ; i++ ))
do
    SMTP_HOST="${RELAY[$i]}"
    echo "Trying relay [$i]: $SMTP_HOST..."
    exec 5<>/dev/tcp/$SMTP_HOST/$SMTP_PORT
    read HELO <&5
    echo GOT: $HELO
    for COMMAND_ORDER in 0 1 2 3 4 5 6 7
    do
            OUT=${SMTP_COMMANDS[COMMAND_ORDER]}
            echo SENDING: $OUT
            echo -e "$OUT\r" >&5

            read -r REPLY <&5
            echo REPLY: $REPLY
            # CODE=($REPLY)
            CODE=${REPLY:0:2}
            ACTION=${SMTP_REPLY[CODE]}
            case $ACTION in
                    WAIT )          echo Temporarily Fail
                                                    break
                                                    ;;
                    FAIL )          echo Failed
                                                    break
                                                    ;;
                    OK )                            ;;
                    SENT )          exit 0
                                                    ;;
                    DATA )          echo Sending Message: $MESSAGE
                                                    cat $MESSAGE >&5
                                                    echo -e "\r" >&5
                                                    ;;
                    * )         echo Unknown SMTP code $CODE
                                                    exit 2
            esac
    done
done
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Orwellophile
  • 13,235
  • 3
  • 69
  • 45
  • I thought this was a really cool script and created a slightly modified version for my own purposes which worked at one time, but after a couple of years, when I tried it, it failed. Not sure if bash has changed or what. Bash complains that the "break" in the "WAIT" case is only meaningful in a 'for', 'while', or 'until' loop. As it is, the "break" is in a "case" section within a "do" loop. I'm 90% sure this used to work just fine. – Marnix A. van Ammers Sep 24 '17 at 19:40
  • Reimplementing an SMTP client in pure Bash is mostly a frustrating effort. This will work fine in the trivial cases, but probably crumble badly when the server doesn't exactly conform to your assumptions. – tripleee Jun 13 '23 at 06:56
  • @tripleee if the SMTP server I wrote 20+ years ago weren't still working perfectly without modification, i might be concerned. Though IPv6 without IPv4 would probably break it. Otherwise, SMTP has a well defined RFC, and that script should function as long as that RFC does not change. https://github.com/sfinktah/dummymail – Orwellophile Jun 27 '23 at 05:57
6

Send mail from Bash with one line:

echo "your mail body" | mail -s "your subject" yourmail@yourdomain.com -a "From: sender@senderdomain.com"
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Oli
  • 1,622
  • 18
  • 14
  • 5
    Got `send-mail: illegal option -- a` on OS X Mavericks – Dima Korobskiy Feb 20 '15 at 23:12
  • 1
    `echo "your mail body" | mail -s "your subject" -F recipient@domain.com` worked for me – mossman Jul 06 '20 at 21:09
  • This is mainly just a reiteration of Brian Postow's answer from 2009, with the same fundamental flaw. Like earlier comments indicate, the `-a` option is not supported by MacOS `mail`. (Perhaps see also https://stackoverflow.com/questions/17359/how-do-i-send-a-file-as-an-email-attachment-using-linux-command-line/48588035#48588035 for an exposition of different `mail` versions.) – tripleee Jun 13 '23 at 06:59
6

sendEmail is a script that you can use to send email from the command line using more complicated settings, including connecting to a remote smtp server: http://caspian.dotconf.net/menu/Software/SendEmail/

On OSX it is easily installable via macports: http://sendemail.darwinports.com/

Below is the help page for the command, take note of the -s, -xu, -xp flags:

Synopsis:  sendEmail -f ADDRESS [options]

Required:
  -f ADDRESS                from (sender) email address
  * At least one recipient required via -t, -cc, or -bcc
  * Message body required via -m, STDIN, or -o message-file=FILE

Common:
  -t ADDRESS [ADDR ...]     to email address(es)
  -u SUBJECT                message subject
  -m MESSAGE                message body
  -s SERVER[:PORT]          smtp mail relay, default is localhost:25

Optional:
  -a   FILE [FILE ...]      file attachment(s)
  -cc  ADDRESS [ADDR ...]   cc  email address(es)
  -bcc ADDRESS [ADDR ...]   bcc email address(es)

Paranormal:
  -xu USERNAME              authentication user (for SMTP authentication)
  -xp PASSWORD              authentication password (for SMTP authentication)
  -l  LOGFILE               log to the specified file
  -v                        verbosity, use multiple times for greater effect
  -q                        be quiet (no stdout output)
  -o NAME=VALUE             see extended help topic "misc" for details

Help:
  --help TOPIC              The following extended help topics are available:
      addressing            explain addressing and related options
      message               explain message body input and related options
      misc                  explain -xu, -xp, and others
      networking            explain -s, etc
      output                explain logging and other output options
Mark Carey
  • 1,567
  • 10
  • 9
4

Here is a simple Ruby script to do this. Ruby ships on the Mac OS X versions you mentioned.

Replace all the bits marked 'replace'. If it fails, it returns a non-zero exit code and a Ruby back trace.

require 'net/smtp'

SMTPHOST = 'replace.yoursmtpserver.example.com'
FROM = '"Your Email" <youremail@replace.example.com>'

def send(to, subject, message)
  body = <<EOF
From: #{FROM}
To: #{to}
Subject: #{subject}

#{message}
EOF
  Net::SMTP.start(SMTPHOST) do |smtp|
    smtp.send_message body, FROM, to
  end
end

send('someemail@replace.example.com', 'testing', 'This is a message!')

You can embed this in a Bash script like so:

ruby << EOF
 ... script here ...
EOF

For some other ways to send Ruby emails, see Stack Overflow question How do I send mail from a Ruby program?.

You can use other languages that ship with Mac OS X as well:

Community
  • 1
  • 1
docwhat
  • 11,435
  • 6
  • 55
  • 54
3

1) Why not configure postfix to handle outbound mail only and relay it via a mail gateway? Its biggest advantage is that it is already installed on OS X clients.

2) Install and configure one of the lightweight MTAs that handle only outbound mail, like nullmailer or ssmtp (available via MacPorts).

In both cases use mailx(1) (or mutt if you want to get fancy) to send the mails from a shell script.

There are several questions on Server Fault that go into the details.

  • I'd like to make it easy to deploy this script on many machines without configuring anything else like postfix so I'd like it to be self contained in the script. I'll look into nullmailer and ssmtp. Is this possible with sendmail or smtpd that comes with OS X? – ridogi Nov 30 '09 at 21:55
  • errr...? sendmail is part of postfix, and postfix is the smtpd on os x, so what is your question? –  Nov 30 '09 at 22:05
  • I didn't know if they are components of postfix or could operate on their own. – ridogi Nov 30 '09 at 22:33
  • Good to know about nullmailer, but I would prefer postfix over having to install anything to simplify deploying this script. I have not been able to find any directions on setting up postfix to handle outbound mail for a mail server that I specify. I assume that I would not be able to specify those settings only in my shell script, but that is ideally how this would work for me. If that isn't possible what config files for postfix need to be modified? – ridogi Dec 16 '09 at 07:05
3

sendmail and even postfix may be too big to install if all you want to do is to send a few emails from your scripts.

If you have a Gmail account for example, you can use Google's servers to send email using SMTP. If you don't want to use gGoogle's server, as long as you have access to some SMTP server, it should work.

A very lightweight program that makes it easy to do so is msmtp. They have examples of configuration files in their documentation.

The easiest way to do it would be to set up a system-wide default:

account default
host smtp.gmail.com
from john.doe@gmail.com
user john.doe@gmail.com
password XXX
port 587

msmtp should be very easy to install. In fact, there is a port for it, so it could be as easy as port install msmtp.

After installing and configuring msmtp, you can send email to john.doe@gmail.com using:

mail -s <subject> john.doe@gmail.com <<EOF
<mail text, as many lines as you want. Shell variables will be expanded>.
EOF

You can put the above in a script. See man mail for details.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alok Singhal
  • 93,253
  • 21
  • 125
  • 158
0

Here's a modified shells script snip I've used on various UNIX systems... (echo "${MESSAGE}" | ${uuencode} ${ATTACHMENT}$basename ${ATTACHMENT}) | ${mailx} -s "${SUBJECT}" "${TO_LIST}"

uuencode and mailx are set to the executables. The other variables are from user input parsed using getopts.

This does work but I have to admit more often than not I use a simple Java program to send console emails.

tmeisenh
  • 1,504
  • 1
  • 13
  • 11
  • Unfortunately, uuencode and mailx are not part of OSX. mail is, though. I'm unclear how much setup is required to make it work well. The from address, by default, is "systemname.local" which is uninformative. :-( – docwhat Dec 21 '09 at 21:38
  • I'm not concerned about the systemname.local being uninformative as I have identifying information in the subject, but I am concerned that the default postfix configuration will be blocked by firewalls. – ridogi Dec 21 '09 at 22:26
  • uuenecode and mailx are in the standard solaris install we have at my work. I _think_ they are standard in FreeBSD but I may have grabbed them from ports, I can't remember. You may look into a perl/python/ruby alternative. – tmeisenh Dec 22 '09 at 18:09
0

Try mtcmail. Its a fairly complete email sender, completely standalone.

motobói
  • 1,687
  • 18
  • 24