User Tools

Site Tools


This is an old revision of the document!

Send Form Letter Email with Bash

I resurrected the script below to help my wife with some work. She had to notify dozens and dozens of people that their projects were accepted to a film festival that she helps manage. The bulk of the email was the same for all recipients – and so she started the work by copying and pasting the email and then, for each new email, changing the name of the recipient, the title of the film, the subject line, and of course entering the new email address but keeping the same CC address for each email. Fortunately, she was able to export this information as a CSV file from the management software that she uses (which I'm not sure why doesn't offer this ability).

Here's the new script:

while read line
    title=$(echo $line | awk -F',' '{print $1}')
    name=$(echo $line | awk -F',' '{print $2}')
    email=$(echo $line | awk -F',' '{print $3}')
    CC=$(echo $line | awk -F',' '{print $4}')
    sed -e "s/<<name>>/$name/g" \
        -e "s/<<title>>/$title/g" letter.html \
        | mutt -c $CC -s "$title + The Name of Film Festival" \
        -e "set content_type=text/html" $email
    sleep 1
done <- accepted.csv

My wife wanted the email to be written in HTML (and look nice), and that's the 'letter.html' file that's read into mutt. Mutt was weird about this part. When I tested the script, I used the following code to format the email as HTML:

-e "set my_hdr Content-Type: text/html" $email

That worked the first time, and I guess is supposed to on the stable release of Mutt, but for some reason that I can not discern, it didn't work the second time and I had to set content type using the line in the script above. Something to investigate later.

The 'accepted.csv' file includes four fields:


The CC address is the same for each line.

The 'letter.html' file contains the main material for each email. When I copied it from her and marked it up in HTML, I changed the name and title in the text that needed to be changed, to




, which made it easier to find and replace using awk and sed. Thus, the letter looked like this:

<p>Dear <<name>>,</p>
<p>I'm pleased to inform you that your film <<title>> has been accepted ...</p>

This script doesn't test for the existence of any of the files, and it's expected that both letter.html and accepted.csv are in the working directory. Those are a couple of things to change if this were to be generalized. Other than that, it worked nicely.

Surely not the most elegant script in the world (in other words, it's super ugly), but it works. I use the following programs to make this work:

For email:

  • mailx (although Mutt should work; it's scriptable)
  • ssmtp

For the rest:

  • BASH
  • awk
  • sed

The script uses two files. The first file contains three columns in the following format:

n1    emailAddress-Main1    emailAddress-CC1
n2    emailAddress-Main2    emailAddress-CC2
n3    emailAddress-Main3    emailAddress-CC3

The second file is a letter I need to email to to the addresses in the first file. In this letter contains the string NUM. I need to put the number from the first column (e.g., n1) in the letter and send that letter to appropriate email addresses in the second and third columns. The letter looks something like this:

Dear So and So:

Your ID# is NUM.  Please use that for X.

Thank you,


Recap: Replace NUM in letter with n1 and send letter to emailAddress-Main1 and emailAddress-CC1. Repeat for line two. So forth.

I have it sleep for 1 second just so it pauses and doesn't overload. It's probably not necessary, but it doesn't hurt.

## Written by Sean Burns, March 2, 2010
while read line
        ID=$(echo $line | awk '{print $1}')
        email=$(echo $line | awk '{print $2}')
        CC=$(echo $line | awk '{print $3'}')
        sed "s/NUM/$ID/g" NameOfLetter | mail -c $CC -s "Subject of Letter" $email
        sleep 1
done < FileContainingEmails

Batch New Accounts

To create new accounts on a Linux system, issue the following commands (or create a script file):

# todo: set up various test conditions to exit out in case of ...
# install libpam_cracklib to force strong passwords (search Internet for notes on configuration)
# create new accounts
# set default complex password
# force users to update password upon logging in
accounts=(user1 user2 user3)
for i in ${accounts[*]} ; do
        /usr/sbin/useradd -mU "$i" &&\
        echo "$i":'complex_password' | chpasswd &&\
        passwd -e "$i"

Another way:

# modified based on a script written by Julia Tran, 2018 July
# name.txt contains a list of usernames only
# userpw contains a list of users and temp passwords in the following format: user1:password1
for i in $(cat name.txt) ; do
  useradd -mU "$i"
chpasswd < userpw
for i in $(cat name.txt) ; do
  passwd -e "$i"
linux/bash.1633455578.txt.gz · Last modified: 2021/10/05 13:39 by seanburns