To deal with the SMS size limit I use this recipe:

    :0c
    * ^TO.*ry4an-phone
    | /usr/local/bin/email2sms | mail 6125557352 at voicestream.net

To send the mail to the attached script.  Which tries a few different
strategies to get the message down to size including the fun
Lingua::EN::Squeeze Perl module.  What's more it then a reply back to
the sender showing them the munged message so they can decide whether
too much important data was lost.  This paragraph run through the filter
looks like:

    To snd  mail to attached scriptW/ trie  few dif 
    stratge to get  msg dwn to siz includng  fun 
    Lng::EN::sqz Prl modWhats +/ it then  reply bck to 
     send showng them  mng msg so t/ _n dcde wheth 
     too mch important dat was lstThi prgra run thrgh  filt
     lok lik

And at the highest level:

    ToSndMailToAttachedScriptW/TrieFewDifStratgeToGetMsgDwnToSizIncludngFunLng::EN::sqzPrlModWhats+/ItThenReplyBckToSendShowngThemMngMsgSoT/_nDcdeWhethTooMchImportantDatWasLstThiPrgraRunThrghFiltLokLik

I don't write Perl as ugly as in the attached script which originally
came from freshmeat[1], but I did add the auto-responding code.

[1] http://freshmeat.net/projects/email2sms/?topic_id=29%2C28

On Wed, Sep 10, 2003 at 10:00:02PM -0500, Chad Walstrom wrote:
 
> It looks good, generally speaking.  I would indent things to make it a
> bit clearer what you're trying to do.  I usually indent "E" and "A"
> recipes as well, just for a visual flow effect.
> 
> :0
> * Condition to match
> { # RECIPE ... }
> 
>     :0E  #The "E" is there because of a previous recipe in my script
>     {
>         :0c
>         ! <myphonenumber>@mobile.att.net
> 
>         :0
>         ! myuserid at server.com
>     }
> 
> I'm not sure how well AT&T filters emails, but T-Mobile's SMS Gateway
> doesn't clean up some header fields very well.  Additionally, the
> "Subject:" and "From:" headers count against your total character count.
> 
>     [snip recipe]

-- 
Ry4an Brase - http://ry4an.org                                    /~\
'If you're not a rebel when you're 20 you've got no heart; if     \ /
 you're not establishment when you're 30 you've got no brain.'     X
             Join the ASCII ribbon campaign against HTML email    / \
-------------- next part --------------
#!/usr/bin/perl -w
##
## email2sms - email to SMS formatter
##
## Copyright (c) 1999--2000 Adam Spiers <adam at spiers.net>. 
## Miniscule portions Copyright (c) 1999 Ry4an Brase <ry4an at ry4an.org>.
##
## All rights reserved. This program is free software; you can redistribute
## it and/or modify it under the same terms as Perl itself.
##
## $Id$
##


use strict;

use Lingua::EN::Squeeze;
use MIME::Entity;
use MIME::Body;
use MIME::Parser;
use Getopt::Std;


##
## Process options and config file
##

my %opts = ();
getopts('f:h', \%opts);

my $configfile = $opts{'f'} || "$ENV{HOME}/.email2smsrc";

if (@ARGV or $opts{h}) {
  die <<USAGE;
email2sms, (c) 1999 Adam Spiers <adam\@spiers.net>

Command-line usage: email2sms [ -f configfile ] < email_in > sms_out

Please see the accompanying README/INSTALL files for full instructions.
USAGE
}

# Configuration defaults
my %conf = (
            maxlen  => 160,
            logfile => '',
            section => '|',
            newline => '|',
            attrib  => '',
            quoted  => '',
            squeeze_modes => [ 'noconv' ],
            optimize => 0,
            respond => 0,
            smtphost => 'localhost',
           );

&parse_config_file($configfile);

# Open log file
if ($conf{logfile}) {
  open(LOG, ">>$conf{logfile}")
    or die "Couldn't open log file $conf{logfile} for appending.\n";
}


##
## Parse and munge e-mail
##

# FIXME: This is a security hole!
my $tmp_dir = "/tmp/email2sms.$>.$$";

&log_this("Using $tmp_dir as MIME temporary directory\n");
mkdir $tmp_dir, 0700 or die "mkdir: $!";
my $parser  = new MIME::Parser;
$parser->output_dir($tmp_dir);

my $mail_in = $parser->read(\*STDIN)
  or die "Couldn't parse STDIN as MIME stream\n";

my $body_in = join '', @{ body $mail_in };

my $why_not = &check_content_type($mail_in);
die "$why_not\n" if $why_not;

# These globals are our scratchpad, and get used by &final_out()
my ($from_in, $from_out, $subject_out, $body_out, $subject_in);

# Munge body first
$body_out = $body_in;
&munge_body($body_in);

# Then munge header, depending on how much we managed to squeeze the body
my $header_in = head $mail_in;
my $header_out = &munge_header($header_in);


##
## Send message
##

my $sms = substr(&final_out, 0, $conf{maxlen});

&log_this("Final message:\n$sms\n");
&log_delim();
&log_this("Final length: ", length($sms), "\n");

if ($conf{respond}) {
  my $matched = eval qq{\$from_in =~ $conf{respond}};
  if ($@) {
    &log_delim();
    &log_this("`respond' regexp $conf{respond} didn't compile:\n  $@\n");
  }
  elsif ($matched) {
    &log_delim();
    &respond($mail_in);
  }
  else {
    &log_delim();
    &log_this("didn't match respond regexp\n");
  }
}

&log_delim('-');

print $sms, "\n";

my $all_tmps = "$tmp_dir/*";
unlink glob($all_tmps) or die "unlink: $!";
rmdir $tmp_dir;

exit 0;


##############################################################################

sub parse_config_file {
  my $config_file = shift;

  open(CONFIG, $config_file)
    or die "Couldn't open config file $config_file: $!\n";
  while (<CONFIG>) {
    next if /^\s*\#/ || /^\s*$/;           # damn cperl-mode

    s/^\s*//;                   # trim leading whitespace

    # This is a butt-ugly switch
    if (/^maxlen\s+(\d+)/) {
      $conf{maxlen} = $1;
    }
    elsif (/^logfile\s+(.*?)\s*$/) {
      ($conf{logfile} = $1) =~ s/~/$ENV{HOME}/g;
      $conf{logfile} =~ s/\$(\w)/$ENV{$1}/g;
    }
    elsif (/^section\s+'(.*)'\s*$/) {
      $conf{section} = $1;
    }
    elsif (/^newline\s+'(.*)'\s*$/) {
      $conf{newline} = $1;
    }
    elsif (/^attrib\s+'(.*)'\s*$/) {
      $conf{attrib} = $1;
    }
    elsif (/^quoted\s+'(.*)'\s*$/) {
      $conf{quoted} = $1;
    }
    elsif (/^fromsub\s+(.*)$/) {
      push @{$conf{from_substs}}, $1;
    }
    elsif (/^squeeze\s+(.*)\s*$/) {
      @{$conf{squeeze_modes}} = split /,\s*/, $1;
    }
    elsif (/^optimize\s+([01])\s*$/) {
      $conf{optimize} = $1;
    }
    elsif (/^respond\s+(.*)\s*$/) {
      $conf{respond} = $1;
    }
    elsif (/^response-from\s+(.*)\s*$/) {
      $conf{response_from} = $1;
    }
    elsif (/^smtphost\s+(.*)\s*$/) {
      $conf{smtphost} = $1;
    }
  }
  close(CONFIG);
}

##

sub check_content_type {
  my ($mail_in) = @_;

  my $why_not = '';

  my $content_type = $mail_in->mime_type;
  &log_this("Content-Type is $content_type\n");
  
  if ($mail_in->is_multipart) {
    &log_this("Message is multipart; splitting ...\n");

    # Get text/plain bits only
    my @parts = $mail_in->parts;
    &log_this(@parts . " parts found\n");

    if (@parts > 0) {
      my @parts_in = ();
      foreach my $part (@parts) {
        my $mime_type = $part->mime_type;
        &log_this("part type $mime_type\n");
        if ($mime_type =~ m!^text/plain!i) {
          push @parts_in, $part->body_as_string();
        }
        else {
          &log_this("Skipping $mime_type attachment\n");
        }
      }
      if (@parts_in) {
        $body_in = join $conf{section}, @parts_in;
      }
      else {
        $why_not = "No text/plain message parts found.";
      }
    }
    else {
      $why_not = "Multipart message had no parts.";
    }
  }

  &log_delim();

  return $why_not;
}

##

sub munge_body {
  my ($body_in) = @_;

  #&log_this("*** Untouched message body:\n$body_in\n");
  #&log_delim();

  # Remove quoted material
  #$body_in =~ s/(^> *.*?$()\n)+//gm;
  $body_in =~ s/
                ( $conf{attrib} \n ) ?           # attribution line
                ( $conf{quoted} .*? $ \n )+      # quoted lines
               //gmx
    if exists $conf{attrib} and exists $conf{quoted};

  &log_this("*** Dequoted message body:\n$body_in\n");
  &log_delim();

  # Newlines collapse ...
  $body_in =~ s/^\n+\s*//;
  $body_in =~ s/\s*\n+\s*/$conf{newline}/g;

  my $mode = 0;
  my @squeeze_modes = @{$conf{squeeze_modes}};
  $Lingua::EN::Squeeze::SQZ_OPTIMIZE_LEVEL = $conf{optimize};

  # Shrink body, but not more than necessary

  do {
    my $new_mode = $squeeze_modes[$mode++];
    &log_this("Trying squeeze mode $new_mode on body ... ");
    SqueezeControl($new_mode);

    $body_out = SqueezeText $body_in;

    # SqueezeText seems to add a \n
    chomp $body_out;

    # It doesn't eliminate multiple consecutive spaces either ... weird
    $body_out =~ s/\s+/ /g;

    &log_this(length(&final_out) . " characters\n");
  } 
  until length(&final_out) <= $conf{maxlen} or $mode > $#squeeze_modes;

  &log_delim();
}

##

sub munge_header {
  my ($header_in) = @_;

  # Who's it from?
  $from_in = $header_in->get('From') || $header_in->get('From ') || '?';
  $from_in =~ s/\w{3} \w{3} \d\d \d\d:\d\d:\d\d \d{4}$//; # remove date

  $from_out = $from_in || '?';

  # Eliminate multiple consecutive spaces
  $from_out =~ s/\s+/ /g;

  if ($from_out) {
    chomp $from_out;
    &munge_from();
  }

  $subject_in = $header_in->get('Subject') || '';
  chomp $subject_in;
  &munge_subject($subject_in) if $subject_in;
}

##

sub munge_from {
  return unless @{$conf{from_substs}};

  my $munger_code = <<'EVAL';
sub {
  my $from = shift;

EVAL

  $munger_code .= join '', 
                       map { '  $from =~ ' . $_ . ";\n" } 
                           @{$conf{from_substs}};
  $munger_code .= <<'EVAL';
  return $from;
}
EVAL

  &log_this("from munger is:\n$munger_code");
  &log_delim();
  my $munger = eval $munger_code;

  &log_this("From header before munging is $from_out\n");
  $from_out = $munger->($from_out);
  &log_this("From header after munging is $from_out\n");
  &log_delim();
}

##

sub munge_subject {
  my ($subject_in) = @_;

  # Shrink subject if we're still over the limit, but not more than necessary

  $subject_out = $subject_in;

  if (length(&final_out) > $conf{maxlen}) {
    my $mode = 0;
    my @squeeze_modes = @{$conf{squeeze_modes}};
    do {
      my $new_mode = $squeeze_modes[$mode++];
      &log_this("Trying squeeze mode $new_mode on subject ... ");
      SqueezeControl($new_mode);

      $subject_out = SqueezeText $subject_in;

      # SqueezeText seems to add a \n
      chomp $subject_out;

      # It doesn't eliminate multiple consecutive spaces either ... weird
      $subject_out =~ s/\s+/ /g;

      &log_this(length(&final_out) . " characters\n");
    }
    until length(&final_out) <= $conf{maxlen} or $mode > $#squeeze_modes;

    &log_delim();
  }
}

##

sub final_out {
  my @sections = ();

  foreach ($from_out, $subject_out, $body_out) {
    my $section = $_;           # stop aliasing effect
    if ($section) {
      # damnit, thought this would have gone by now
      $section =~ s/^\s*(.+?)\s*$/$1/;
      push @sections, $section;
    }
  }

  return join $conf{section}, @sections;
}

##

sub log_this {
  return unless $conf{logfile};

  print LOG @_;
}

##

sub log_delim {
  my $delimiter = shift;
  $delimiter ||= '. ';
  &log_this(substr($delimiter x 80, 0, 79), "\n");
}

##

sub respond {
  my $mail = shift;

  my $body = <<EOF;
THIS IS AN AUTOMATICALLY GENERATED MESSAGE:

Your message has been converted for display on Ry4an's cell phone.
The converted output appears below.  If you believe that information
vital to the understanding of the message has been truncated or lost
during compression please shorten your message or split it into
multiple messages and resend it.  The full text of the message is in
Ry4an's mailbox but his phone will only show the shortened version.

To send a message without this conversion send it to
ry4an-raw-phone\@ry4an.org.  Messages sent to that address will be
truncated after 130 characters (about two lines of text).

Message sent to Ry4an's phone:

EOF

  $body .= substr(&final_out, 0, $conf{maxlen});

  my $to = $from_in;
  chomp $to;

  my $reply = MIME::Entity->build(From => $conf{response_from}, 
                                  To => $to,
                                  Subject => $subject_in,
                                  Type => 'text/plain',
                                  Encoding => 'quoted-printable',
                                  Data => [ $body ]);
                                  
  &log_this("Responding by email to: $to\n");
  
  # Send the autoreply
  my @sent_to = $reply->smtpsend(Host => $conf{smtphost})
    or warn "failed to send auto-reply";
}