+=item send
+
+Sends the message through its parent L<FS::msg_template>. Returns an error
+message on error, or an empty string.
+
+=cut
+
+sub send {
+ my $self = shift;
+ # 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);
+}
+
+=item entity
+
+Returns the complete message as a L<MIME::Entity>.
+
+XXX this only works if the message in fact contains a MIME entity. Messages
+created by external APIs may not look like that.
+
+=item parts
+
+Returns a list of the MIME parts contained in the message, as L<MIME::Entity>
+objects.
+
+=cut
+
+sub entity {
+ my $self = shift;
+ if ( !exists($self->{entity}) ) {
+ my $parser = MIME::Parser->new;
+ my $output_dir = "$FS::UID::cache_dir/cache.$FS::UID::datasrc/mimeparts";
+ mkdir($output_dir) unless -d $output_dir;
+ $parser->output_under($output_dir);
+ $self->{entity} =
+ $parser->parse_data( $self->header . "\n" . $self->body );
+ }
+ $self->{entity};
+}
+
+sub parts {
+ my $self = shift;
+ # return only the parts with bodies, not the multipart containers
+ grep { $_->bodyhandle } $self->entity->parts_DFS;
+}
+
+=back
+
+=head1 SUBROUTINES
+
+=over 4
+
+=item process_send CUSTMSGNUM
+
+Given a C<cust_msg.custmsgnum> value, sends the message. It must already
+have been prepared (via L<FS::msg_template/prepare>).
+
+=cut
+
+sub process_send {
+ my $custmsgnum = shift;
+ my $cust_msg = FS::cust_msg->by_key($custmsgnum)
+ or die "cust_msg #$custmsgnum not found";
+ my $error = $cust_msg->send;
+ die $error if $error;
+}
+
+sub _upgrade_schema {
+ my ($class, %opts) = @_;
+
+ my $sql = '
+ DELETE FROM cust_msg WHERE NOT EXISTS
+ ( SELECT 1 FROM cust_main WHERE cust_main.custnum = cust_msg.custnum )
+ ';
+
+ my $sth = dbh->prepare($sql) or die dbh->errstr;
+ $sth->execute or die $sth->errstr;
+ '';
+
+}
+