#21564, external message services: preview and send messages through the UI
authorMark Wells <mark@freeside.biz>
Fri, 28 Aug 2015 02:18:42 +0000 (19:18 -0700)
committerMark Wells <mark@freeside.biz>
Fri, 28 Aug 2015 02:18:42 +0000 (19:18 -0700)
FS/FS/Schema.pm
FS/FS/cust_main_Mixin.pm
FS/FS/cust_msg.pm
FS/FS/msg_template.pm
FS/FS/msg_template/email.pm
httemplate/misc/email-customers.html

index 311313a..12211d1 100644 (file)
@@ -6400,6 +6400,7 @@ sub tables_hashref {
         'error',     'varchar', 'NULL',    255, '', '',
         'status',    'varchar',     '',$char_d, '', '',
         'msgtype',   'varchar', 'NULL',     16, '', '',
+        'preview',      'text', 'NULL',     '', '', '',
       ],
       'primary_key'  => 'custmsgnum',
       'unique'       => [ ],
index bdad511..3d05f84 100644 (file)
@@ -445,6 +445,10 @@ sub email_search_result {
   my $success = 0;
   my %sent_to = ();
 
+  if ( !$msg_template ) {
+    # XXX create on the fly
+  }
+
   #eventually order+limit magic to reduce memory use?
   foreach my $obj ( qsearch($sql_query) ) {
 
@@ -459,36 +463,19 @@ sub email_search_result {
     }
 
     my $cust_main = $obj->cust_main;
-    tie my %message, 'Tie::IxHash';
     if ( !$cust_main ) { 
       next; # unlinked object; nothing else we can do
     }
 
-    if ( $msg_template ) {
-      # Now supports other context objects.
-      %message = $msg_template->prepare(
-        'cust_main' => $cust_main,
-        'object'    => $obj,
-      );
-    }
-    else {
-      my @to = $cust_main->invoicing_list_emailonly;
-      next if !@to;
-
-      %message = (
-        'from'      => $from,
-        'to'        => \@to,
-        'subject'   => $subject,
-        'html_body' => $html_body,
-        'text_body' => $text_body,
-        'custnum'   => $cust_main->custnum,
-      );
-    } #if $msg_template
+    my $cust_msg = $msg_template->prepare(
+      'cust_main' => $cust_main,
+      'object'    => $obj,
+    );
 
     # For non-cust_main searches, we avoid duplicates based on message
-    # body text.  
+    # body text.
     my $unique = $cust_main->custnum;
-    $unique .= sha1($message{'text_body'}) if $class ne 'FS::cust_main';
+    $unique .= sha1($cust_msg->text_body) if $class ne 'FS::cust_main';
     if( $sent_to{$unique} ) {
       # avoid duplicates
       $dups++;
@@ -497,18 +484,20 @@ sub email_search_result {
 
     $sent_to{$unique} = 1;
     
-    $error = send_email( generate_email( %message ) );
+    $error = $cust_msg->send;
 
     if($error) {
       # queue the sending of this message so that the user can see what we
       # tried to do, and retry if desired
+      # (note the cust_msg itself also now has a status of 'failed'; that's 
+      # fine, as it will get its status reset if we retry the job)
       my $queue = new FS::queue {
-        'job'        => 'FS::Misc::process_send_email',
+        'job'        => 'FS::cust_msg::process_send',
         'custnum'    => $cust_main->custnum,
         'status'     => 'failed',
         'statustext' => $error,
       };
-      $queue->insert(%message);
+      $queue->insert($cust_msg->custmsgnum);
       push @retry_jobs, $queue;
     }
     else {
index 9346327..ec2c961 100644 (file)
@@ -47,8 +47,12 @@ from FS::Record.  The following fields are currently supported:
 
 =item body - message body (as a complete MIME document)
 
+=item preview - HTML fragment to show as a preview of the message
+
 =item error - Email::Sender error message (or null for success)
 
+=item status - "prepared", "sent", or "failed"
+
 =back
 
 =head1 METHODS
@@ -137,6 +141,7 @@ sub check {
     || $self->ut_textn('env_to')
     || $self->ut_anything('header')
     || $self->ut_anything('body')
+    || $self->ut_anything('preview')
     || $self->ut_enum('status', \@statuses)
     || $self->ut_textn('error')
     || $self->ut_enum('msgtype', [  '',
@@ -159,8 +164,9 @@ message on error, or an empty string.
 
 sub send {
   my $self = shift;
-  my $msg_template = $self->msg_template
-    or return 'message was created without a template object';
+  # it's still allowed to have cust_msgs without message templates, but only 
+  # for email.
+  my $msg_template = $self->msg_template || 'FS::msg_template::email';
   $msg_template->send_prepared($self);
 }
 
index 180e9de..d7d9f50 100644 (file)
@@ -10,6 +10,8 @@ use FS::Record qw( qsearch qsearchs );
 use FS::cust_msg;
 use FS::template_content;
 
+use Date::Format qw(time2str);
+
 FS::UID->install_callback( sub { $conf = new FS::Conf; } );
 
 $DEBUG=0;
index 1133faa..275dc82 100644 (file)
@@ -26,11 +26,12 @@ use FS::Record qw( qsearch qsearchs );
 use FS::template_content;
 use FS::UID qw( dbh );
 
+# needed to manage prepared messages
 use FS::cust_msg;
 
 FS::UID->install_callback( sub { $conf = new FS::Conf; } );
 
-our $DEBUG = 1;
+our $DEBUG = 0;
 our $me = '[FS::msg_template::email]';
 
 =head1 NAME
@@ -362,74 +363,12 @@ sub prepare {
       'error'     => '',
       'status'    => 'prepared',
       'msgtype'   => ($opt{'msgtype'} || ''),
+      'preview'   => $body, # html content only
   });
 
   return $cust_msg;
 }
 
-=item send_prepared CUST_MSG
-
-Takes the CUST_MSG object and sends it to its recipient.
-
-=cut
-
-sub send_prepared {
-  my $self = shift;
-  my $cust_msg = shift or die "cust_msg required";
-
-  my $domain = 'example.com';
-  if ( $cust_msg->env_from =~ /\@([\w\.\-]+)/ ) {
-    $domain = $1;
-  }
-
-  my @to = split(/\s*,\s*/, $cust_msg->env_to);
-
-  my %smtp_opt = ( 'host' => $conf->config('smtpmachine'),
-                   'helo' => $domain );
-
-  my($port, $enc) = split('-', ($conf->config('smtp-encryption') || '25') );
-  $smtp_opt{'port'} = $port;
-  
-  my $transport;
-  if ( defined($enc) && $enc eq 'starttls' ) {
-    $smtp_opt{$_} = $conf->config("smtp-$_") for qw(username password);
-    $transport = Email::Sender::Transport::SMTP::TLS->new( %smtp_opt );
-  } else {
-    if ( $conf->exists('smtp-username') && $conf->exists('smtp-password') ) {
-      $smtp_opt{"sasl_$_"} = $conf->config("smtp-$_") for qw(username password);     
-    } 
-    $smtp_opt{'ssl'} = 1 if defined($enc) && $enc eq 'tls';
-    $transport = Email::Sender::Transport::SMTP->new( %smtp_opt );
-  }
-
-  warn "$me sending message\n" if $DEBUG;
-  my $message = join("\n\n", $cust_msg->header, $cust_msg->body);
-  local $@;
-  eval {
-    sendmail( $message, { transport => $transport,
-                          from      => $cust_msg->env_from,
-                          to        => \@to })
-  };
-  my $error = '';
-  if(ref($@) and $@->isa('Email::Sender::Failure')) {
-    $error = $@->code.' ' if $@->code;
-    $error .= $@->message;
-  }
-  else {
-    $error = $@;
-  }
-
-  $cust_msg->set('error', $error);
-  $cust_msg->set('status', $error ? 'failed' : 'sent');
-  if ( $cust_msg->custmsgnum ) {
-    $cust_msg->replace;
-  } else {
-    $cust_msg->insert;
-  }
-
-  $error;
-}
-
 =item render OPTION => VALUE ...
 
 Fills in the template and renders it to a PDF document.  Returns the 
@@ -491,183 +430,6 @@ my $usage_warning = sub {
   return ['', '', ''];
 };
 
-#my $conf = new FS::Conf;
-
-#return contexts and fill-in values
-# If you add anything, be sure to add a description in 
-# httemplate/edit/msg_template.html.
-sub substitutions {
-  { 'cust_main' => [qw(
-      display_custnum agentnum agent_name
-
-      last first company
-      name name_short contact contact_firstlast
-      address1 address2 city county state zip
-      country
-      daytime night mobile fax
-
-      has_ship_address
-      ship_name ship_name_short ship_contact ship_contact_firstlast
-      ship_address1 ship_address2 ship_city ship_county ship_state ship_zip
-      ship_country
-
-      paymask payname paytype payip
-      num_cancelled_pkgs num_ncancelled_pkgs num_pkgs
-      classname categoryname
-      balance
-      credit_limit
-      invoicing_list_emailonly
-      cust_status ucfirst_cust_status cust_statuscolor cust_status_label
-
-      signupdate dundate
-      packages recurdates
-      ),
-      [ invoicing_email => sub { shift->invoicing_list_emailonly_scalar } ],
-      #compatibility: obsolete ship_ fields - use the non-ship versions
-      map (
-        { my $field = $_;
-          [ "ship_$field"   => sub { shift->$field } ]
-        }
-        qw( last first company daytime night fax )
-      ),
-      # ship_name, ship_name_short, ship_contact, ship_contact_firstlast
-      # still work, though
-      [ expdate           => sub { shift->paydate_epoch } ], #compatibility
-      [ signupdate_ymd    => sub { $ymd->(shift->signupdate) } ],
-      [ dundate_ymd       => sub { $ymd->(shift->dundate) } ],
-      [ paydate_my        => sub { sprintf('%02d/%04d', shift->paydate_monthyear) } ],
-      [ otaker_first      => sub { shift->access_user->first } ],
-      [ otaker_last       => sub { shift->access_user->last } ],
-      [ payby             => sub { FS::payby->shortname(shift->payby) } ],
-      [ company_name      => sub { 
-          $conf->config('company_name', shift->agentnum) 
-        } ],
-      [ company_address   => sub {
-          $conf->config('company_address', shift->agentnum)
-        } ],
-      [ company_phonenum  => sub {
-          $conf->config('company_phonenum', shift->agentnum)
-        } ],
-      [ selfservice_server_base_url => sub { 
-          $conf->config('selfservice_server-base_url') #, shift->agentnum) 
-        } ],
-    ],
-    # next_bill_date
-    'cust_pkg'  => [qw( 
-      pkgnum pkg_label pkg_label_long
-      location_label
-      status statuscolor
-    
-      start_date setup bill last_bill 
-      adjourn susp expire 
-      labels_short
-      ),
-      [ pkg               => sub { shift->part_pkg->pkg } ],
-      [ pkg_category      => sub { shift->part_pkg->categoryname } ],
-      [ pkg_class         => sub { shift->part_pkg->classname } ],
-      [ cancel            => sub { shift->getfield('cancel') } ], # grrr...
-      [ start_ymd         => sub { $ymd->(shift->getfield('start_date')) } ],
-      [ setup_ymd         => sub { $ymd->(shift->getfield('setup')) } ],
-      [ next_bill_ymd     => sub { $ymd->(shift->getfield('bill')) } ],
-      [ last_bill_ymd     => sub { $ymd->(shift->getfield('last_bill')) } ],
-      [ adjourn_ymd       => sub { $ymd->(shift->getfield('adjourn')) } ],
-      [ susp_ymd          => sub { $ymd->(shift->getfield('susp')) } ],
-      [ expire_ymd        => sub { $ymd->(shift->getfield('expire')) } ],
-      [ cancel_ymd        => sub { $ymd->(shift->getfield('cancel')) } ],
-
-      # not necessarily correct for non-flat packages
-      [ setup_fee         => sub { shift->part_pkg->option('setup_fee') } ],
-      [ recur_fee         => sub { shift->part_pkg->option('recur_fee') } ],
-
-      [ freq_pretty       => sub { shift->part_pkg->freq_pretty } ],
-
-    ],
-    'cust_bill' => [qw(
-      invnum
-      _date
-      _date_pretty
-      due_date
-    ),
-      [ due_date2str      => sub { shift->due_date2str('short') } ],
-    ],
-    #XXX not really thinking about cust_bill substitutions quite yet
-    
-    # for welcome and limit warning messages
-    'svc_acct' => [qw(
-      svcnum
-      username
-      domain
-      ),
-      [ password          => sub { shift->getfield('_password') } ],
-      [ column            => sub { &$usage_warning(shift)->[0] } ],
-      [ amount            => sub { &$usage_warning(shift)->[1] } ],
-      [ threshold         => sub { &$usage_warning(shift)->[2] } ],
-    ],
-    'svc_domain' => [qw(
-      svcnum
-      domain
-      ),
-      [ registrar         => sub {
-          my $registrar = qsearchs('registrar', 
-            { registrarnum => shift->registrarnum} );
-          $registrar ? $registrar->registrarname : ''
-        }
-      ],
-      [ catchall          => sub { 
-          my $svc_acct = qsearchs('svc_acct', { svcnum => shift->catchall });
-          $svc_acct ? $svc_acct->email : ''
-        }
-      ],
-    ],
-    'svc_phone' => [qw(
-      svcnum
-      phonenum
-      countrycode
-      domain
-      )
-    ],
-    'svc_broadband' => [qw(
-      svcnum
-      speed_up
-      speed_down
-      ip_addr
-      mac_addr
-      )
-    ],
-    # for payment receipts
-    'cust_pay' => [qw(
-      paynum
-      _date
-      ),
-      [ paid              => sub { sprintf("%.2f", shift->paid) } ],
-      # overrides the one in cust_main in cases where a cust_pay is passed
-      [ payby             => sub { FS::payby->shortname(shift->payby) } ],
-      [ date              => sub { time2str("%a %B %o, %Y", shift->_date) } ],
-      [ payinfo           => sub { 
-          my $cust_pay = shift;
-          ($cust_pay->payby eq 'CARD' || $cust_pay->payby eq 'CHEK') ?
-            $cust_pay->paymask : $cust_pay->decrypt($cust_pay->payinfo)
-        } ],
-    ],
-    # for payment decline messages
-    # try to support all cust_pay fields
-    # 'error' is a special case, it contains the raw error from the gateway
-    'cust_pay_pending' => [qw(
-      _date
-      error
-      ),
-      [ paid              => sub { sprintf("%.2f", shift->paid) } ],
-      [ payby             => sub { FS::payby->shortname(shift->payby) } ],
-      [ date              => sub { time2str("%a %B %o, %Y", shift->_date) } ],
-      [ payinfo           => sub {
-          my $pending = shift;
-          ($pending->payby eq 'CARD' || $pending->payby eq 'CHEK') ?
-            $pending->paymask : $pending->decrypt($pending->payinfo)
-        } ],
-    ],
-  };
-}
-
 =item content LOCALE
 
 Returns the L<FS::template_content> object appropriate to LOCALE, if there 
@@ -684,168 +446,84 @@ sub content {
             { 'msgnum' => $self->msgnum, 'locale' => '' });
 }
 
-=item agent
-
-Returns the L<FS::agent> object for this template.
-
 =cut
 
-sub _upgrade_data {
-  my ($self, %opts) = @_;
+=back
 
-  ###
-  # First move any historical templates in config to real message templates
-  ###
+=head2 CLASS METHODS
 
-  my @fixes = (
-    [ 'alerter_msgnum',  'alerter_template',   '',               '', '' ],
-    [ 'cancel_msgnum',   'cancelmessage',      'cancelsubject',  '', '' ],
-    [ 'decline_msgnum',  'declinetemplate',    '',               '', '' ],
-    [ 'impending_recur_msgnum', 'impending_recur_template', '',  '', 'impending_recur_bcc' ],
-    [ 'payment_receipt_msgnum', 'payment_receipt_email', '',     '', '' ],
-    [ 'welcome_msgnum',  'welcome_email',      'welcome_email-subject', 'welcome_email-from', '' ],
-    [ 'warning_msgnum',  'warning_email',      'warning_email-subject', 'warning_email-from', '' ],
-  );
-  my @agentnums = ('', map {$_->agentnum} qsearch('agent', {}));
-  foreach my $agentnum (@agentnums) {
-    foreach (@fixes) {
-      my ($newname, $oldname, $subject, $from, $bcc) = @$_;
-      if ($conf->exists($oldname, $agentnum)) {
-        my $new = new FS::msg_template({
-          'msgname'   => $oldname,
-          'agentnum'  => $agentnum,
-          'from_addr' => ($from && $conf->config($from, $agentnum)) || '',
-          'bcc_addr'  => ($bcc && $conf->config($from, $agentnum)) || '',
-          'subject'   => ($subject && $conf->config($subject, $agentnum)) || '',
-          'mime_type' => 'text/html',
-          'body'      => join('<BR>',$conf->config($oldname, $agentnum)),
-        });
-        my $error = $new->insert;
-        die $error if $error;
-        $conf->set($newname, $new->msgnum, $agentnum);
-        $conf->delete($oldname, $agentnum);
-        $conf->delete($from, $agentnum) if $from;
-        $conf->delete($subject, $agentnum) if $subject;
-      }
-    }
+=over 4
 
-    if ( $conf->exists('alert_expiration', $agentnum) ) {
-      my $msgnum = $conf->exists('alerter_msgnum', $agentnum);
-      my $template = FS::msg_template->by_key($msgnum) if $msgnum;
-      if (!$template) {
-        warn "template for alerter_msgnum $msgnum not found\n";
-        next;
-      }
-      # this is now a set of billing events
-      foreach my $days (30, 15, 5) {
-        my $event = FS::part_event->new({
-            'agentnum'    => $agentnum,
-            'event'       => "Card expiration warning - $days days",
-            'eventtable'  => 'cust_main',
-            'check_freq'  => '1d',
-            'action'      => 'notice',
-            'disabled'    => 'Y', #initialize first
-        });
-        my $error = $event->insert( 'msgnum' => $msgnum );
-        if ($error) {
-          warn "error creating expiration alert event:\n$error\n\n";
-          next;
-        }
-        # make it work like before:
-        # only send each warning once before the card expires,
-        # only warn active customers,
-        # only warn customers with CARD/DCRD,
-        # only warn customers who get email invoices
-        my %conds = (
-          'once_every'          => { 'run_delay' => '30d' },
-          'cust_paydate_within' => { 'within' => $days.'d' },
-          'cust_status'         => { 'status' => { 'active' => 1 } },
-          'payby'               => { 'payby'  => { 'CARD' => 1,
-                                                   'DCRD' => 1, }
-                                   },
-          'message_email'       => {},
-        );
-        foreach (keys %conds) {
-          my $condition = FS::part_event_condition->new({
-              'conditionname' => $_,
-              'eventpart'     => $event->eventpart,
-          });
-          $error = $condition->insert( %{ $conds{$_} });
-          if ( $error ) {
-            warn "error creating expiration alert event:\n$error\n\n";
-            next;
-          }
-        }
-        $error = $event->initialize;
-        if ( $error ) {
-          warn "expiration alert event was created, but not initialized:\n$error\n\n";
-        }
-      } # foreach $days
-      $conf->delete('alerter_msgnum', $agentnum);
-      $conf->delete('alert_expiration', $agentnum);
-
-    } # if alerter_msgnum
+=item send_prepared CUST_MSG
 
-  }
+Takes the CUST_MSG object and sends it to its recipient. This is a class 
+method because everything needed to send the message is stored in the 
+CUST_MSG already.
 
-  ###
-  # Move subject and body from msg_template to template_content
-  ###
+=cut
 
-  foreach my $msg_template ( qsearch('msg_template', {}) ) {
-    if ( $msg_template->subject || $msg_template->body ) {
-      # create new default content
-      my %content;
-      $content{subject} = $msg_template->subject;
-      $msg_template->set('subject', '');
-
-      # work around obscure Pg/DBD bug
-      # https://rt.cpan.org/Public/Bug/Display.html?id=60200
-      # (though the right fix is to upgrade DBD)
-      my $body = $msg_template->body;
-      if ( $body =~ /^x([0-9a-f]+)$/ ) {
-        # there should be no real message templates that look like that
-        warn "converting template body to TEXT\n";
-        $body = pack('H*', $1);
-      }
-      $content{body} = $body;
-      $msg_template->set('body', '');
+sub send_prepared {
+  my $self = shift;
+  my $cust_msg = shift or die "cust_msg required";
 
-      my $error = $msg_template->replace(%content);
-      die $error if $error;
-    }
+  my $domain = 'example.com';
+  if ( $cust_msg->env_from =~ /\@([\w\.\-]+)/ ) {
+    $domain = $1;
   }
 
-  ###
-  # Add new-style default templates if missing
-  ###
-  $self->_populate_initial_data;
-
-}
+  my @to = split(/\s*,\s*/, $cust_msg->env_to);
 
-sub _populate_initial_data { #class method
-  #my($class, %opts) = @_;
-  #my $class = shift;
+  my %smtp_opt = ( 'host' => $conf->config('smtpmachine'),
+                   'helo' => $domain );
 
-  eval "use FS::msg_template::InitialData;";
-  die $@ if $@;
+  my($port, $enc) = split('-', ($conf->config('smtp-encryption') || '25') );
+  $smtp_opt{'port'} = $port;
+  
+  my $transport;
+  if ( defined($enc) && $enc eq 'starttls' ) {
+    $smtp_opt{$_} = $conf->config("smtp-$_") for qw(username password);
+    $transport = Email::Sender::Transport::SMTP::TLS->new( %smtp_opt );
+  } else {
+    if ( $conf->exists('smtp-username') && $conf->exists('smtp-password') ) {
+      $smtp_opt{"sasl_$_"} = $conf->config("smtp-$_") for qw(username password);     
+    } 
+    $smtp_opt{'ssl'} = 1 if defined($enc) && $enc eq 'tls';
+    $transport = Email::Sender::Transport::SMTP->new( %smtp_opt );
+  }
 
-  my $initial_data = FS::msg_template::InitialData->_initial_data;
+  warn "$me sending message\n" if $DEBUG;
+  my $message = join("\n\n", $cust_msg->header, $cust_msg->body);
+  local $@;
+  eval {
+    sendmail( $message, { transport => $transport,
+                          from      => $cust_msg->env_from,
+                          to        => \@to })
+  };
+  my $error = '';
+  if(ref($@) and $@->isa('Email::Sender::Failure')) {
+    $error = $@->code.' ' if $@->code;
+    $error .= $@->message;
+  }
+  else {
+    $error = $@;
+  }
 
-  foreach my $hash ( @$initial_data ) {
+  $cust_msg->set('error', $error);
+  $cust_msg->set('status', $error ? 'failed' : 'sent');
+  if ( $cust_msg->custmsgnum ) {
+    $cust_msg->replace;
+  } else {
+    $cust_msg->insert;
+  }
 
-    next if $hash->{_conf} && $conf->config( $hash->{_conf} );
+  $error;
+}
 
-    my $msg_template = new FS::msg_template($hash);
-    my $error = $msg_template->insert( @{ $hash->{_insert_args} || [] } );
-    die $error if $error;
+=back
 
-    $conf->set( $hash->{_conf}, $msg_template->msgnum ) if $hash->{_conf};
-  
-  }
+=cut
 
-}
+# internal use only
 
 sub eviscerate {
   # Every bit as pleasant as it sounds.
@@ -897,8 +575,6 @@ sub eviscerate {
   (\@outside, \@inside);
 }
 
-=back
-
 =head1 BUGS
 
 =head1 SEE ALSO
index 8ac44af..bffd0cf 100644 (file)
@@ -1,10 +1,11 @@
 <%doc>
 
-Allows emailing one or more customers, based on a search for customers.  Search can
-be specified either through cust_main fields as cgi params, or through a base64 encoded
-frozen hash in the 'search' cgi param.  Form allows selecting an existing msg_template,
-or creating a custom message, and shows a preview of the message before sending.
-If linked to as a popup, include the cgi parameter 'popup' for proper header handling.
+Allows emailing one or more customers, based on a search for customers.
+Search can be specified either through cust_main fields as cgi params, or
+through a base64 encoded frozen hash in the 'search' cgi param.  Form allows
+selecting an existing msg_template, or creating a custom message, and shows a
+preview of the message before sending.  If linked to as a popup, include the
+cgi parameter 'popup' for proper header handling.
 
 This may also be used as an element in other pages, enabling you to provide an
 alternate initial form while using this for search freezing/thawing and 
@@ -21,12 +22,13 @@ title - the title of the page
 no_search_fields - arrayref of additional fields that are not search parameters
 
 alternate_form - subroutine that returns alternate html for the initial form,
-replaces msgnum/from/subject/html_body/action inputs and submit button,
-not used if an action is specified
+replaces msgnum/from/subject/html_body/action inputs and submit button, not
+used if an action is specified
 
-post_search_hook - sub hook for additional processing after search has been processed from cgi,
-gets passed options 'conf' and 'search' (a reference to the unfrozen %search hash),
-should be used to set msgnum or from/subject/html_body cgi params
+post_search_hook - sub hook for additional processing after search has been
+processed from cgi, gets passed options 'conf' and 'search' (a reference to
+the unfrozen %search hash), should be used to set msgnum or
+from/subject/html_body cgi params
 
 </%doc>
 % if ($popup) {
@@ -288,8 +290,13 @@ if ( $cgi->param('action') eq 'preview' ) {
       'cust_main' => $cust,
       'object' => $object,
     );
-    my %message = $msg_template->prepare(%msgopts);
-    ($from, $subject, $html_body) = @message{'from', 'subject', 'html_body'};
+
+    my $cust_msg = $msg_template->prepare(%msgopts);
+    $from = $cust_msg->env_from;
+    $html_body = $cust_msg->preview;
+    if ( $cust_msg->header =~ /^subject: (.*)/mi ) {
+      $subject = $1;
+    }
   }
 }