Email from U2 Hold Files as PDFs

Many years ago, my client wanted to start emailing reports from our UniVerse systems. We had several ksh scripts that tried to build the headers for using Unix mail to send them out. Each time a new report was dreamed up, a new script was requested. After four or five of these that only sort-of worked, I started to look for a better way. One of the problems was that PCs had become a major tool and when employees would try to look at the reports on their PC it wasn't usually a pleasant experience.

Printing those emailed reports was even worse. I decided that we were missing a valuable tool because we were ignoring the PCs and what they could do for the employee. While talking to one of my Unix admins, he came up with a tool to convert text into a PDF.

In Solaris there is postprint and in Linux there is enscript . I wrote a small script that my developers could call so that they could convert any text into a PDF [Figure 1] . This puts the printout into a form that can be easily viewed and printed by a PC without any special knowledge on the end users' part, a real consideration when the report could be going to any one of 15,000 employees; from the President to the warehouse workers.

# convert text file in hold file into pdf and send to portal
# $1 = file name
# $2 = source directory
# $3 = destination directory
# $4 = (L or P) source Landscape or Portrait
# $5 = remove
sysname=`uname -n`
cd $2
if [ "$orientation" = "L" ]
# cat $1 | /usr/lib/lp/postscript/postprint -pl -m.8 -x-.03 -y.24 - > /tmp/$1
# cat $1 | /usr/bin/enscript -B --font=Courier8 -r -1 --margins=9:9:36:36 -o /tmp/$1
# changing to enscript codes to emulate what printers do fas1 10/20/10
cat $1 | /usr/bin/enscript -B --font=Courier8 -r -i1c -1 --margins=28:0:0:-24 -o /tmp/$1
# cat $1 | /usr/lib/lp/postscript/postprint -pp -m1.2 -x-.01 -y.24 - > /tmp/$1
cat $1 | /usr/bin/enscript -B --font=Courier11 -R --margins=18:18:9:9 -o /tmp/$1
/usr/bin/ps2pdf /tmp/$1 /tmp/$1.pdf
cp -p /tmp/$1.pdf $3
rm /tmp/$1.pdf /tmp/$1
if [ "$remove" = "remove" ]
    rm -f $2/$1
exit 0

This didn't solve the email problem but did have a hidden benefit: when emailed, the pdf report is much smaller than the original text report. I have used the same process to create the driver for my Unix/Linux printers that print UniVerse reports [Figure 2]. Most printers on the network are now Windows printers that the Unix/Linux world borrow. They are postscript enabled.

# /usr/lib/lp/postscript/postprint -pl -m.8 -x-.03 -y.24 - | lp -o nobanner -d top2
# awk -f /usr/local/bin/lttr_lnd66.awk | lp -d top2
/usr/bin/enscript -hBZ --font=Courier8 -r -i1c -1 --margins=10:0:0:-15 -u"De
v" --ul-style=filled --ul-font=Times-Roman120 -P top2

This also allows me to put a watermark on any printouts coming from test and development systems. That solved a worry of mine about test/development reports escaping into the "wild" with no indication of where they originated.

So, now I have the ability to get text files printed, but the email was still a problem. I was using Google to search for some way to solve the email problem and found a Perl program that packages attachments and emails them. The program is called and was written by Steve Ho. ( can be found below).

use strict;
use warnings;
# Based on mail_attach 1.0 By Steven Ho. 
# The accepted file names are:
# 1. file with full path, such as /a/b/c/filename
# 2. files in the current directory, such as filename.
# 3. if mimencode exists it uses mimencode, otherwise, use uuencode
# Modifications to original:
# Convert from interactive to batch script.
# Added "Usage:" display.
# Added "use strict" and "use warnings" and cleaned up all the
# complaints.
# Added the pid to temporary file names so that more than one
# process can use this script in a given account simultaneously.
# Lots of general cleanup.
# Future modifications:
# Copying the attachments to the local directory prior to running
# them through the encoding process is probably not necessary but
# I'd have to investigate uuencode and mimencode to make sure.
# If those utilities do not modify the original file, that code
# can go away.

$ENV{'PATH'} = "/bin:/usr/sbin:/usr/bin:/usr/local/bin:/usr/lib";

my $boundary = "simple boundary";
my $WC = "wc";
my $CP = "cp";
my $UNLINK = "unlink";
my $MV = "mv";
my $SENDMAIL = "sendmail";
my $TMP = "/tmp";
my $TMPONLY = $$ . ".tmponly";
my $ENCODED = $$ . ".encoded";
my $ATTACH = $$ . ".encoded_attach";

# Find out which utility to use to encode the emails.  If "mimencode"
# is available use it, otherwise use "uuencode".
my $en_test = `which mimencode 2>&1`;
my $use_mimencode = ($en_test !~ /\bno mimencode\b/);

my $DEBUG = 0;
print "\$use_mimencode = $use_mimencode\n" if ($DEBUG);

my $runControlRef = {};
my $validDomainList = $runControlRef->{"VALID_DOMAINS"};
my $validAddressList = $runControlRef->{"VALID_ADDRESSES"};
my $defaultDomain = $runControlRef->{"DEFAULT_DOMAIN"};
my $from = $runControlRef->{"FROM_ADDRESS"};
my $overrideTo = $runControlRef->{"OVERRIDE_TO_ADDRESS"};
my $removeAllFiles = 0;

usage() if ($#ARGV < 1);

chomp @ARGV;

my $to = shift;# destination address for the email
my $subject_line = shift;# string for subject line
my $content;        # name of file containing body of message
my @attachments;         # name of file(s) containing attachment(s).
my @filesToDelete;              # list of files to be deleted after run
my $originalTo = "";# only filled in if using an override address 

foreach my $arg (@ARGV) {
    if ($arg =~ "^-[rR]") {
        print "Delete files after emailing:  TRUE\n" if ($DEBUG);
        $removeAllFiles = 1;
    next if ($arg eq '');
    my ($key, $value) = split(/=/,$arg);
    if ($key eq "BODY") {
        die "BODY keyword used with no body filename given!" unless ($value);
        die "$value: file not found!\n" unless (-e $value);
        $content = $value;
    elsif ($key eq "ATTACH") {
        my @tmp_attach = split /,/, $value;
        die "ATTACH keyword used with no attachments listed!" unless ($value);
        foreach (@tmp_attach) {
            die "$_: file not found!\n" unless (-e $_);
            push(@attachments, $_);
    else {

unless ($content || @attachments) {
    die "Must include a message body file or attachment file.\n"

# Check for override destination address.
if (defined($overrideTo) && ($overrideTo ne "")) {
  if ($DEBUG) {
    print "Overriding destination address.\n";
    print "\tOriginal destination: $to\n";
    print "\tNew destination: $overrideTo\n";
  $originalTo = $to;
  $to = $overrideTo;

# Validate the destination address and fix the domain if necessary.
if ($to !~ /@/) {
  my ($mailbox, @trash) = split(/@/, $to);
  $to = $mailbox . $defaultDomain;
  print "Added or corrected domain: $to\n" if ($DEBUG);
} elsif ($to =~ /($validDomainList)$/i) {
  print "Found destination in valid domain list.\n" if ($DEBUG);
} elsif (($validAddressList ne "") && ($to =~ /^($validAddressList)$/i)) {
  print "Found destination in valid address list.\n" if ($DEBUG);
} else {
  die "Unauthorized destination address or domain.\n";

# Establish the sender address and validate it.
if (defined($from) && ($from ne "")) {
  print "Original \$from address = $from\n" if ($DEBUG);
  $from = "-f$from";
  if ($from !~ /@/) {
    $from = $from . $defaultDomain;
  print "After massaging, \$from = $from\n" if ($DEBUG);
  if ($from =~ /($validDomainList)$/i) {
    print "Found sender address in valid domain list.\n" if ($DEBUG);
  } elsif (($validAddressList ne "") && ($from =~ /^($validAddressList)$/i)) {
    print "Found sender address in valid address list.\n" if ($DEBUG);
  } else {
    warn "Unauthorized sender address or domain.  Using default instead.\n";
    $from = "";
} else {
  print "No from address specified.\n" if ($DEBUG);

print "Preparing for delivery... This may take a while...\n";

# Prepare the header of the mail message
my $content_length = 0;
if ($content) {
    my $quotedcontent = $content;
    $quotedcontent =~ s/\&/\\&/g;
#   $content_length = (split(' ',`$WC $content`))[2];
    $content_length = (split(' ',`$WC $quotedcontent`))[2];
    if ($removeAllFiles) {
       push(@filesToDelete, $quotedcontent);

if ($originalTo ne "") {
  $subject_line .= " (Originally to: $originalTo)";

open(CODED, ">$TMP/$ATTACH") || die "can't open $TMP/$ATTACH";
print CODED "Subject: $subject_line\n";
print CODED "MIME-Version: 1.0\n";
print CODED "Content-Type: multipart/mixed; boundary=\"$boundary\"\n";
print CODED "Content-Length: $content_length\n";
print CODED "\n";
print CODED "--$boundary\n";
print CODED "Content-Type: text/plain; charset=ISO-8859-1\n";
# If you enable the following two lines, your message will show
# up as an attachment.
#print CODED "Content-Transfer-Encoding: 7bit\n";
#print CODED "Content-Description: \"Message Content\"\n";
print CODED "\n";

# print the body of the message (if there is one) out to the encoded file.
if ($content) {
   open(BODY, "$content") || die "can't open $content";
   while(<BODY>) {
       print CODED $_;
#close (CODED);

if (@attachments) {
    # Create an encoded file (with attachment header) for every
    # attachment.
    for(@attachments) {
        my $rename = 0;
        my $remove = 0;
        my $rv_f_name;
        my $encoding;
        if (/\//) { 
            my $quotedpath = $_;
            $quotedpath =~ s/\&/\\&/g;
            $rv_f_name = (reverse(split('/', $_)))[0];
            if (-e $rv_f_name) {
                print "$rv_f_name exists.  Temporarily renaming\n" if ($DEBUG);
                system("$MV $rv_f_name $TMPONLY");
#               system("$CP $_ .");
                system("$CP $quotedpath .");
                $rename = 1;
    else {
#               system("$CP $_ .");
        system("$CP $quotedpath .");
                $remove = 1;
            if ($removeAllFiles) {
                push(@filesToDelete, $quotedpath);
        else {
            $rv_f_name = $_;
            $remove = 0;
            if ($removeAllFiles) {
                push(@filesToDelete, $rv_f_name);
# If the filename ends in ".txt" run "unix2dos" against it in-place before
# encoding it for attachment.  To avoid an annoying message about not being
# able to open /dev/kbd to determine the code page to use, I've included the
# "-437" parameter to the unix2dos call to force it to use the US code page.
        if ($rv_f_name =~ /\.txt$/) {
#           system("unix2dos -437 $rv_f_name $rv_f_name");
            system("lar_unix2dos.plx -437 $rv_f_name $rv_f_name");
        if($use_mimencode) {
            print "Calling mimencode\n" if ($DEBUG);
            system("mimencode -o $TMP/$ENCODED $rv_f_name");
            $encoding = "base64";
        else {
            print "Calling lar_mimencode.plx\n" if ($DEBUG);
#           system("uuencode -m $rv_f_name $rv_f_name > $TMP/$ENCODED");
            system("lar_mimencode.plx $rv_f_name $TMP/$ENCODED");
#           $encoding = "uuencode";
            $encoding = "base64";
#       $output = `$WC $TMP/$ENCODED`;
#       ($line, $words, $length, $name) = split(' ', $output);

#       open(CODED, ">>$TMP/$ATTACH") || die "can't open $TMP/$ATTACH";
        open(OLD_CODED, "$TMP/$ENCODED") || die "can't open $TMP/$ENCODED";
        print CODED "--$boundary\n";
        print CODED "Content-Type: application/octet-stream; name=\"$rv_f_name\"\n";
        print CODED "Content-Transfer-Encoding: $encoding\n";
        print CODED "\n";
        while(<OLD_CODED>) {
            print CODED $_;
        print CODED "\n";
        system("$UNLINK $rv_f_name") if $remove == 1;
        system("$MV $TMPONLY $rv_f_name") if $rename == 1;
        $rename = $remove = 0;
} # end of attachments block

print CODED "--$boundary--\n";

my $sendmail_rc = 0;
$sendmail_rc = system("$SENDMAIL $from $to < $TMP/$ATTACH");
if ($sendmail_rc == 0) {
  print "Sent to: $to\n";
} else {
# if this happens, lookup the value in /usr/include/sysexits.h - @TODO
  $sendmail_rc /= 256;
  print "$SENDMAIL failed:  RC=$sendmail_rc\n";

system("$UNLINK $TMP/$ATTACH");
if (@attachments) {
    system("$UNLINK $TMP/$ENCODED");

if ($removeAllFiles) {
    foreach my $fileToDelete (@filesToDelete) {
       print "Deleting file $fileToDelete\n";
       system("$UNLINK $fileToDelete")

# ~~~~~~~~~~~~ end of script ~~~~~~~~~~~~~

sub usage {
    print "usage: address subject [BODY=filename] [ATTACH=filename[,filename]] -r\n\t-r removes all source files after emailing them\n";

sub loadRunControl {
  $runControlRef = shift;

# Read the run control file and put the contents in the runControl hash.
  open (RC, "/usr/local/bin/mail_attach.rc") || die "Cannot open run control file:  mail_attach.rc\n";
  while(<RC>) {
    next if (/^#/);
    next unless (/^\s*(\w+)=(\S*)/);
    $runControlRef->{$1} = $2;
  close RC;

# Make sure that there's a default domain specified.
  if (!defined($runControlRef->{"DEFAULT_DOMAIN"})) {
    print "\nNo default domain defined.  Make sure that mail_attach.rc contains an\n";
    print "entry that looks like:\n\n";
    print "\tDEFAULT_DOMAIN=somedomain\n\n";

# Prepend the "@" symbol to the default domain if it doesn't start with one .
  my $testDefaultDomain = $runControlRef->{"DEFAULT_DOMAIN"};
  if ($testDefaultDomain !~ /^@/) {
    print "Prepending \"@\" to default domain.\n" if ($DEBUG);
    $testDefaultDomain = "@" . $testDefaultDomain;
    $runControlRef->{"DEFAULT_DOMAIN"} = $testDefaultDomain;

# Prepend the "@" symbol to each domain that doesn't already start with one.
  my $validDomainList = $runControlRef->{"VALID_DOMAINS"};
  $validDomainList = join('|',map {
    if (/^[^@]/) {
      "@" . $_;
    } else {
  } split(/,/,$validDomainList));
  $runControlRef->{"VALID_DOMAINS"} = $validDomainList;
  print "Valid Domain List: $validDomainList\n" if ($DEBUG);

# Stitch the valid addresses together as a pipe-delimited pattern
  my $validAddressList = $runControlRef->{"VALID_ADDRESSES"};
  $validAddressList = join('|', split(/,/, $validAddressList));
  $runControlRef->{"VALID_ADDRESSES"} = $validAddressList;
  print "Valid Address List: $validAddressList\n" if ($DEBUG);

It handles all of the header elements for the developer. If you are sending attachments, it handles that, too. The developer only needs to know what parameters to send to I have made a few enhancements to it. We also added code to "jail" emails from our test and development systems. There is a configuration file for and we added code and parameter so we could redirect all emails to a single mailbox, retaining the original address.

I've included two example commands from the mvBASIC programs [Figure 3].

execute "SH -c ' ": '/usr/local/bin/printout.converter " ':JOB.ID:' " " ':SRC.DIR:' " " ':DEST.DIR:' " " ':PG.ORIENT:' " " ':REMOVE:' " ":" ' "
execute "SH -c ' ": '/usr/local/bin/ " ':EMAIL.RECIP:' " " ':SUBJECT:' " "ATTACH=':SRC.PATH:' " " ':REMOVE:' " ':" ' "

Combining these techniques gave us a single, standardized way to send e-mails automatically.

Felix Stellman

View more articles


Jan/Feb 2017