package MOJO::Mail; 
=pod

=head1 NAME

MOJO::Mail

=head1 SYNOPSIS

	use MOJO::Mail

	my $mh = MOJO::Mail->new;

Mail Routines for the Mojo Mail MLM, UC?

=head1 DESCRIPTION


Cool name huh? You have found the heart of the beast, this is where ALL mailings will find 
themselves, one way or another, let's see if we can get this all straightened out so you 
can customize this to your heart's delight. Follow me...

First off, there are TWO different ways mailings happen, either by sending one e-mail, using 
the send() method, or when sending to a group, using the bulk_send() method. This is 
somewhat of a fib, since mailings called by bulk_send() actually use the send() method to 
do its dirty work. Well, that is, if you're not using a SMTP feature, then both the send() 
and bulk_send() have their own way of doing it... kinda. 

MOJO::Mail uses the Mail::Bulkmail CPAN module, 
written by James A Thomason III (thomasoniii@yahoo.com) for ALL its SMTP services. And we 
pretty much love him for that. 

B<PLEASE NOTE> That the version of Mail::Bulkmail.pm provided in this distribution B<IS NOT>
the same version as is on CPAN, the version in this distribution has 2 bug fixes, one relating
to having the correct date applied to messages, the other relating to the fact that there are 
top level domains with more than 3 letters in 'em.

You create a single address object like so: 

	my $mh = MOJO::Mail->new(\%list_info); 
	my %mailing = (
	To       => 'justin@skazat.com', 
	From     => 'alex@prolix.nu', 
	Subject  => 'party over here!', 
	Body     => 'yo yo yo, wheres the flava at? we need some action!', 
	); 
	$mh->bulk_send(%mailing);


Pretty fricken hard eh? 
Well, it can get a bit harder than that, but thats a pretty stripped down version, 
If you wanted, you could theoretically use this as a do all mail sender + all the features 
that are in the Guts.pm module. Its pretty easy to make some crazy... stuff once you've got a 
handle on it. 

=cut

use lib '.'; 
use lib '../'; 
use lib './'; 
use lib 'MOJO';
use Fcntl qw(
LOCK_SH
O_RDONLY
O_CREAT
);


use MOJO::Config; 
use MOJO::Guts; 
use MOJO::List;

use MOJO::Log;
my $log =  new MOJO::Log;
		
use strict; 
use vars qw($AUTOLOAD); 
use Carp; 
my %allowed = (
	list_info        => {},
	
	list_type        => 'list',
	
	bulk_test        => 0,
	bulk_start_email => undef, 
	bulk_start_num   => undef, 
	
	do_not_send_to   => [],

); 

=pod

=head1 new

new should take a reference to a hash which is the list settings hash, 
gotten from open_database() in Guts.pm. Why? cause the list settings have
all sorts of... well settings to change how things get sent. Can you call 
this module without this hash reference? Yes! you may, you can even fib the 
hash reference, if you want: 

	my $mh = MOJO::Mail->new({this => 'that'}) 
	

please note that you need at least a list name for bulk sending. The list 
name is needed to open the actual subscription list to send out to people. 	

you can use just the send() method without anything passed to new

=head1 Default Headers

MOJO::Mail has a wide variety of both e-mail headers you can send to it. you do this 
through either the B<send()> or B<bulksend()> methods

=over 4

=item * From 

This should hold where the e-mail is from. Tricky, eh? 

example: 

	To => '"Justin Simoni" <justin@skazat.com>', 

=item * To 

Mail Header, This should hold where the e-mail is going to, 

example: 

	From =>  '"Alex Skazat" <alex@skazat.com>',

=item * Bcc

This is used as a "Blank Carbon Copy", meaning a copygoes to whoever is 
specified in the Bcc header, without it showing up in the To's header. 
B<DO NOT USE THIS FOR BULK MAILING> Send one 100,000 emails, some poor fella 
is going to get 100,000 copies 

example: same as the To or From 

=item * Return-Path 

Specifies what address an e-mail will get sent to when someone replies to an e-mail address. 
Sometimes works, sometimes doesn't, very fluky, better to set with the sendmail '-f' flag 

example: 

	'Return-Path' => 'justin@skazat.com', 

=item * Reply-To

Very similar to the Return-Path, 

=item * Errors-To 

Mail Header, specifies where errors, like if the e-mail address you're trying to send 
isn't real, or their mailbox is full,  a message I<supposed> to  use this address 
to mail the error to. 

example: 

	'Errors-To' => 'errors@skazat.com',

=item * Organization 

Mail Header, um, specify an organization, this could be pretty mch anything you want. 

example: 

	Organization => 'Curtled Yogart Appreciation Club', 


=item * Precedence 

Mail Header, sets the so called 'precedence' of a bulk message, the valid values are
list, bulk and junk. Certain e-mail clients may use this to filter out spam, or 
list messages, 

example: 

	Precedence => 'junk', 

default is list. 


=item * Content-Type 

sets the content type of the email message, something like text/plain or text/html, it tells the mail reader how to 
show the mail message. This is also where you can specify certain character sets, since not everyone uses english as their first language, 

like: 

	Content-Type => 'text/html; charset=us-ascii';


=item * Content-Disposition 

(ah hem) "Whether a MIME body part is to be shown inline or is an attachment; can also indicate a suggested filename for use when saving an attachment to a file."
Yeah... 
I usually use this for HTML and Multipart messages, and I just have it for the ride so emails  come out correctly when using mojo_send.pl, as Microsoft Entourage, Outlook like to use this header, 

example: 

	'Content-Disposition =>  'inline; filename="index.html"',

=item * Content-Transfer-Encoding 

(ah hem) "Coding method used in a MIME message body."

example: 

	'Content-Transfer-Encoding' =>  7bit',

you could also say 'base64', '8bit' or 'quote-printable'

=item * MIME-Version

this specifies what MIME version your using, usually this is set as 1.0, without setting a MIMIE version HTML e-mails don't come out well, 

example: 

	'MIME-Version' => '1.0',

=item * List 

This can be a mail header, mojo uses it internally to say what list is sending bulk messages, the list name is important to fetch all sorts of things, write temp files, etc, 

example List => 'my list', 

=item * Mailing List Headers 

Theres a whole slew of Mailing List related headers, that are pretty self-explainatory: 

List-Archive   

List-Digest 

List-Help

List-ID

List-Owner

List-Post

List-Subscribe

List-Unsubscribe

List-URL

List-Software


=item * References 

this tag is used by popular Mail readers to figure out how different messages are related. 

This isn't used in Mojo Mail, but it may one day for its archives, this usually holds a weird numerical value, 

=item * In-Reply-To 

Again, this is used by mail readers to keep track of e-mail messages.

=item * Subject 

The subject of your message 

example: 

	Subject => 'mail server is about to explode', 



=item * Body

This is the body message, usually I make the value before hand, and stick the variable in, like this: 

	my $Body = <<EOF 
	
	Help! 
	I'm trapped in a peanut butter factory, 
	The only way I can communicate with the 
	real world is by sending messages inside
	peanut butter containers. 
	
		Bob, 
	EOF
	; 

	Body => $Body, 


This really isn't a mail header, but this is how you get the Body of a message to Mojo Mail
=back

B<Please Note>

In earlier versions of this module, certain key/value pairs were passed to this script to change the way mailings were done. No more. Settings that afect mailings are either passed in the hash ref you passed to new() or, by some handy dandy methods


=head1 Handy Dandy Methods 

These methods are used to change how Mojo Mail sends mostly Bulk messages, these are the fun ones, 


=over 4

=item * do_not_send_to

This ones kinda cool, give it a reference to an array of addresses you do not want sent the email. I use this for mojo-send, to make sure that that whoever you write the e-mail to, won't also receive the e-mail, or that will go on forever, 

you could also specify your entire black list. The possibilities, kids, are endless. 

example: $mh->do_not_send_to(['justin@skazat.com',  'alex@prolix.nu']);

This array ref is then passed to List::*::create_bulk_sending_file function that makes the actual list to send to. Basically create_bulk_sending_file weeds out the emails in a list we don't want to send to

=item * bulk_test          

This is a magical paramater that, when changed to one, will only send bulk messages to the list owner. "What's the point of that?" well, it'll set up MOJO::Mail as I<if> it was sending a bulk message, instread of being in "send one message mode" This makes sure all your configs are peachy keen. 1 for test, nothing for no test, 

=back

=cut


# these are all the headers mojo understands. 
# if you don't WANT a header shown, in ANY 
# message, simple take it outta here. 

my %defaults = (
 From                       =>    undef,
 To                         =>    undef,
 Cc                         =>    undef, 
 Bcc                        =>    undef, 
'Return-Path'               =>    undef, 
'Reply-To'                  =>    undef, 
'Errors-To'                 =>    undef, 
 Organization               =>    undef, 
 Precedence                 =>    'list',
 'X-Priority'               =>    undef,
'Content-Type'              =>    undef, 
'Content-Disposition'       =>    undef, 
'Content-Transfer-Encoding' =>    undef, 
'MIME-Version'              =>    undef, 
'Content-Base'              =>    undef, 
 List                 		=>    undef, 
 
'List-Archive'       		=>    undef, 
'List-Digest'         		=>    undef, 
'List-Help'          	    =>    undef, 
'List-ID'                   =>    undef, 
'List-Owner'                =>    undef, 
'List-Post'                 =>    undef, 
'List-Subscribe'            =>    undef, 
'List-Unsubscribe'          =>    undef, 
'List-URL'                  =>    undef,  
'List-Software'             =>   "$PROGRAM_NAME $VER ", 
 References           		=>    undef,
'In-Reply-To'         		=>    undef, 
 Body                 		=>    'blank', 



 Subject               		=>   '(no subject)',  


); 
 
my @default_headers = qw(
From
To
Cc
Bcc
Return-Path
Reply-To
In-Reply-To
Errors-To
References
Organization
MIME-Version 
List
List-Archive
List-Digest
List-Help
List-ID
List-Owner
List-Post
List-Subscribe
List-Unsubscribe
List-URL
List-Software
Precedence
X-Priority
Subject
Content-Type
Content-Transfer-Encoding
Content-Disposition
Content-Base); 
			    
			    



sub new { 
	my $that = shift; 
	my $class = ref($that) || $that; 
	my $self = {
		_permitted => \%allowed, 
		%allowed,
	};
	bless $self, $class;
	my $list_info = shift; 
	$self->{list_info} = $list_info;
	
	$self->_init; 
	
	return $self; 
}

sub AUTOLOAD { 
    my $self = shift; 
    my $type = ref($self) 
    	or croak "$self is not an object"; 
    	
    my $name = $AUTOLOAD;
    $name =~ s/.*://; #strip fully qualifies portion 
    
    unless (exists  $self -> {_permitted} -> {$name}) { 
    	croak "Can't access '$name' field in object of class $type"; 
    }    
    if(@_) { 
        return $self->{$name} = shift; 
    } else { 
        return $self->{$name}; 
    }
}


sub _init { 
	my $self = shift; 
	
	# passing the $list_info hashref is *optional*
	unless($self->{list_info}->{smtp_server}){ 
		$self->{list_info}->{smtp_server} = $SMTP_ADDRESS if $SMTP_ADDRESS;
	}
}


=pod

=head2 return_headers


	my %headers = $mh->return_headers($string); 

This is a funky little subroutine that'll take a string that holds the 
header of a mail message, and gives you back a hash of all the headers 
seperated, each key in the hash holds a different header, so if I say

	my $mh = MOJO::Mail -> new(); 
	my %headers = $mh -> return_headers($header_glob); 


I can then say: 

	my $to = $headers{To}; 

This subroutine is used quite a bit to take out put from the MIME::Lite 
module, which allows you to get the whole header with its header_to_string() 
subroutine and hack it up into something Mojo Mail can use. 

=cut


 
sub return_headers { 
my $self = shift; 

#get the blob
my $header_blob = shift || "";

#init a new %hash
my %new_header;

# split.. logically
my @logical_lines = split /\n(?!\s)/, $header_blob;
 
    # make the hash
    foreach my $line(@logical_lines) {
          my ($label, $value) = split(/:\s*/, $line, 2);
          $new_header{$label} = $value;
        }
return %new_header; 

}

# a bit of data munging 


=pod 

=head2 clean_headers

	%squeaky_clean_headers = $mh->clean_headers(%these_be_the_heaers);

this method does a little munging to the mail headers for better absorbtion; basically, it changes the case of some of the mail headers so everyone's on the same page

=cut




sub clean_headers { 
	my $self = shift; 
	my %mail_headers = @_; 
	$mail_headers{'Content-Type'} = $mail_headers{'Content-type'} 
		if defined $mail_headers{'Content-type'};
	$mail_headers{'Content-Transfer-Encoding'} = $mail_headers{'Content-transfer-encoding'} 
		if defined $mail_headers{'Content-transfer-encoding'};
	$mail_headers{'Content-Base'} = $mail_headers{'Content-base'} 
		if defined $mail_headers{'Content-base'};
	$mail_headers{'Cc'} = $mail_headers{'CC'} 
		if defined $mail_headers{'CC'};
	foreach(keys %mail_headers){ 
		$mail_headers{$_} =~ s/\n$//;
	}
	
	return %mail_headers; 
}

=pod

=head2 send

This method sends an email, it takes a hash of the mail headers, plus the body of the message: 

$mh->send(To      => 'justin@skazat.com', 
		  From    => 'secret@admirer.com', 
		  Subject => 'smooch!', 
		  Body    => 'you are so cute, you little Perl Coder you'
	); 

=cut



sub send{
my $self = shift; 
my %fields = (%defaults,  $self->_make_general_headers, $self->_make_list_headers, @_); 


# quick interpolation like
$defaults{Body} =~ s/\[mojo_url\]/$MOJO_URL/g; 


# sometimes SMTP servers bitch that they can't deliver a message, 
# since the To: and From: headers contain way too much info. 
# This Takes whatever we are passing as the To: and From: and
# just extracts the adress 



%fields = $self->_strip_fields(%fields) if $self->{list_info}->{strip_message_headers} == 1; 	   

### end extraction thing

# there's two ways we send messages (now), one is by a sendmail 
# program (works for most people, not very efficient) and by SMTP 
# (not everyone has access to, crazy fast) This is where we make 
# or choice and then go with it. 
# btw, I like Sweet Potatoes... and apples. 

# if:
# There is an smtp address specified, 
# and this list wants messages sent via SMTP...




if($self->{list_info}->{smtp_server} ne "" and $self->{list_info}->{send_via_smtp} eq "1"){ 

	$self->_pop_before_smtp;  
		
	# we'll send it using SMTP
    # right now, Mojo Mail uses the excellent Mail::Bulkmail 
    # package to send BOTH bulk sending and individual messages. 
    # Its a bit of an overkill to use Mail::Bulkmail for one
    # little message, but its simpler to have one module that 
    # does SMTP sending then two I think. I haven't tested the 
    # efficiency at all though.     
    
	require Mail::Bulkmail; 

	# Mail::Bulkmail likes its recipients passed to it as an array 
	# ref, (this is one of many ways it likes to work) so we need to
	# place the one addy we have in one
	
	my @send = ("$fields{To}"); 
     
    # make an array of headers.. 
	my %bulk_send_headers; 
	
		
		
	foreach my $field (@default_headers){
		$bulk_send_headers{$field} = $fields{$field} if defined $fields{$field}; # only if we have it!
	}	

	$bulk_send_headers{'Content-Type'} .= '; charset='. $self->{list_info}->{charset_value} if(defined($self->{list_info}->{charset_value}) && (defined($bulk_send_headers{'Content-Type'})));
	
	
	# pass the array red
	$bulk_send_headers{"LIST"} = \@send;
	# pass the actual body
	$bulk_send_headers{"Message"} = $fields{Body};

	# pass the smtp address (whistle while you work) 
	$bulk_send_headers{"Smtp"} = $self->{list_info}->{smtp_server};

	# I add a 'SMTP' label to the List-Software header, just 
	# to help me in testing
	$bulk_send_headers{'List-Software'} .= " SMTP";
	$bulk_send_headers{'Return-Path'} = $self->{list_info}->{admin_email} if($self->{list_info}->{print_return_path_header} == 1);
		
		
	$bulk_send_headers{'ERRFILE'} = $SMTP_ERROR_LOG if(defined($SMTP_ERROR_LOG) && ($SMTP_ERROR_LOG ne '')); 

	# create a new bulkmail object, 
	# pass the info to it
	my $bulk = Mail::Bulkmail->new(%bulk_send_headers) 
		or die Mail::Bulkmail->error();
	# the Mail::Bulkmail likes to bitch at the most convenient 
	# times I thinks. 

	# I put trusting to one, just to make things a bit easier to work with. 
	# see the Mail::Bulkmail docs to see what this does. 

	$bulk->Trusting(1) if $SMTP_TRUSTING == 1; 
	# send away
	$bulk->bulkmail() or die Mail::Bulkmail->error();
	$log->mj_log($self->{list_info}->{list}, 'mail_sent', "recipient:$bulk_send_headers{To}, subject:$bulk_send_headers{Subject}") if $LOG{mailings};    
		
# or... 
# we sent it to sendmail for delivery. 
}else{
    
    $MAIL_SETTINGS = $MAIL_SETTINGS . ' -f '. $self->{list_info}->{admin_email} if $self->{list_info}->{add_sendmail_f_flag} == 1;
    
    # pipe it to sendmail.. $MAIL_SETTINGS is set in the Config.pm file... worth a good look.  
	open(MAIL,$MAIL_SETTINGS) or $self->_send_die($fields{Debug});		
		# write the header, if its set.
		
		
		$fields{'Content-Type'} .= '; charset='. $self->{list_info}->{charset_value} if(defined($self->{list_info}->{charset_value}) && (defined($fields{'Content-Type'})));
		
		print MAIL 'Return-Path: <' .  $self->{list_info}->{admin_email} .'>' ."\n" if($self->{list_info}->{print_return_path_header} == 1);
		
		foreach my $field (@default_headers){
				print MAIL "$field: $fields{$field}\n" if defined $fields{$field};
			}
		
	
			
		# a newline always seperates a mail header from its body  				
		print MAIL "\n"; # if Mail->smart();   # little stupid joke, perhaps better: if $self -> smart_ass(); 
		#warn  "\n"; # if Mail->smart();   # little stupid joke, pe
		#write the message
		print MAIL "$fields{Body}\n";
		close(MAIL) or warn "didn't close pipe to '$MAIL_SETTINGS' - $!";  
		# success!
		$log->mj_log($self->{list_info}->{list}, 'Mail Sent', "recipient:$fields{To}, subject:$fields{Subject}") if $LOG{mailings};     
   		return 1; 
		}
}



=cut
sub bulk_send { 
my $self = shift; 
my %fields = (%defaults,@_); 


# bulk_send is just a switch place to either send via SMTP (preffered, 
# or pipe to sendmail (not preffered, but doable)

#SMTP?
if(($SMTP_ADDRESS ne "") && 
   ($fields{send_via_smtp} eq "1") && 
   ($fields{List_Batch} ne "1")){ 
	_bulk_send_via_smtp(%fields); 
}else{ 
	_bulk_send_via_pipe(%fields); 
}

sub bulk_send_via_smtp { 
	my $self = shift; 
	my %fields = @_;


}


=cut

=pod

=head2 bulk_send

Sends a message to everyone on your list, (that you specified, by passing a hash ref with the list settings.. right?) 

Takes the same arguments as send()

=cut



sub bulk_send { 

	my $self = shift; 
	my %fields = (%defaults, $self->_make_general_headers, $self->_make_list_headers, @_); 
	
	# bulk sending can be done in one or two ways, just like regular 
	# sending. We use either sendmail or SMTP. Unless you're sending 
	# a message using SMTP, and you DON'T have any sort of batching 
	# going on, the actual sending uses the $self -> send() method. 
	# hey, why use the same code twice. 
	
	# Remember, the Best way to send a Bulk message is to use SMTP 
	# and NOT have any sort of batch sending. Batch sending was created 
	# for sendmail delivery, as to not destry the Mail Server. SMTP 
	# delivery uses the Mail::Bulkmail module. Batching will actually 
	# send each mailing, by SMTP one at a time. You won't see any performance
	# gains by this, since the whole idea is that the Mail::Bulkmail 
	# package keeps only one connection alive and sends all its mail 
	# at once. 
	
	# anyhoo.. 
	
	
	# $MOJO_URL is a global variable we can deal with it here, no problem
	$defaults{Body} =~ s/\[mojo_url\]/$MOJO_URL/g; 
	
	# sometimes SMTP servers bitch that they can't deliver a message, 
	# since the To: and From: headers contain way too much info. 
	# This Takes whatever we are passing as the To: and From: and
	# just extracts the adress 
	
	%fields = $self->_strip_fields(%fields) if $self->{list_info}->{strip_message_headers} == 1; 	 

	# ok, we've got a big list comin at us, the first thing we want to do is 
	# create a mirror list, so we won't have the FILEHANDLE open for what could 
	# theoretically be DAYS. We'll give it a unique name, so more than one bulk mailing
	# can go on at once. We'll include the PIN in the file also, so we can be lazy later. 
	
	my $lh = MOJO::List->new(-List => $self->{list_info}->{list}, -Path => $FILES);
	
	my $path_to_list = $lh->create_bulk_sending_file(-List      => $self->{list_info}->{list}, 
													 -ID        => $fields{'List-ID'},
													 -Ban       => $self->{do_not_send_to},
													 -Bulk_Test => $self->{bulk_test},
													 -Type      => $self->{list_type});	
	# like i said, there's always two ways to send a message, 
	# one is SMTP, the other is through the sendmail program, 
	# 
	
	# if: 
	# we have an smtp address to work with, and 
	# We're supposed to send via SMTP and 
	# list batching IS NOT 1.. 
	if(($self->{list_info}->{smtp_server} ne "") && ($self->{list_info}->{send_via_smtp} eq "1") && ($self->{list_info}->{enable_bulk_batching} ne "1")){ 
		# send via SMTP.. remember, this is THE BEST AND MOST
		# EFFICIENT WAY TO SEND BULK MESSAGES. 
		$self->bulk_send_bulk_smtp(-fields    =>{%fields},
								   -list_file => $path_to_list);
	}else{

	# else we send it using sendmail. 
	# this is a bit tricky, since we have to do batching here, 
	# as well. Its quite a bitch. lets go!
	
	# how long do we what between batches? 
	my $seconds    = 0; 
	
	if(defined($self->{list_info}->{bulk_sleep_amount})){ 
		$seconds = $self->{list_info}->{bulk_sleep_amount};
	} 
	
	# how many messages get sent between batches? 
	my $letters = 1; 
	if(defined($self->{list_info}->{bulk_send_amount})){ 
		$letters = $self->{list_info}->{bulk_send_amount};
	}
	
	# hey, do we send as a batch at all!?
	my $send_batch = 0; 
	
	if(defined($self->{list_info}->{enable_bulk_batching})){ 
		$send_batch = $self->{list_info}->{enable_bulk_batching}; 
	} 
	
	
	
		# we need to create a new file that has the subscribers and their pin 
		# number. Those two things will be seperated with a '::' so we can split 
		# it apart later.
		 
		my $path_to_list = $lh->create_bulk_sending_file(-List      => $self->{list_info}->{list}, 
														 -ID        => $fields{'List-ID'},
														 -Ban       => $self->{do_not_send_to},
														 -Bulk_Test => $self->{bulk_test},
														 -Type      => $self->{list_type});


# for forking fool
	my $pid; 
	
	FORK: {
		if ($pid = fork) {
			# parent here
			# child process pid is available in $pid
			return 1;
		} elsif (defined $pid) { # $pid is zero here if defined
			# child here
			# parent process pid is available with getppid
			
			
			my $mailing; 
			my $n_letters = $letters; 
			my $n_people  = 0; 
			my $sleep_num = 0; 
			my $send_body = $fields{Body}; 
			my $batch_num = 1; 
						
			# this is annoyingly complicated											
			my $mail_info;
			my $list_pin;		
			my $mailing_count; 									
			my $stop_email;
			my $mailing_amount;
			
			# Start the mailing at a specified number 
			my $start_num = $self->{bulk_start_num}; 
			my $start_num_lock  = 0; # when it's one, it's locked
			my $start_num_count = 0; 
			if($start_num){ 
				$start_num_lock = 1; 
			}
			
			#####################################################################
			# Start the mailing at a specified email address
			# this is helpful if, for some reason, your mailing was dropped. 
			my $start_email = $self->{bulk_start_email}; 
			my $start_email_lock=0; # when it's one, it's locked.
			if($start_email){ 
				$start_email_lock = 1; 
				# we've locked it, and will only start mailings 
				# when it becomes 0 again. 
			}
			
			
			# let's take count of the start time
			my ($ssec, $smin, $shour, $sday, $smonth, $syear) = (localtime)[0,1,2,3,4,5];
			my $mail_start_time = sprintf("Mailing Started: %02d/%02d/%02d %02d:%02d:%02d",  $smonth+1, $sday, $syear+1900, $shour, $smin, $ssec);	
	
			# ok, now lets open that list up
			sysopen(MAILLIST, $path_to_list,  O_RDONLY|O_CREAT, $FILE_CHMOD) or 
				die "$PROGRAM_NAME $VER Error: can't open mailing list to send a Batch Message: $!"; 
			flock(MAILLIST, LOCK_SH) or warn "$PROGRAM_NAME $VER Warning - cannot lock sending file $!"; 
				
			# while we have people on the list.. 
			while(defined($mail_info = <MAILLIST>)){ 		
				# rid of the dreaded new line...
				chomp($mail_info);		
				# get the email, and its pin...
				
				my @ml_info = split('::', $mail_info); 
				my $mailing  = $ml_info[0];
				my $list_pin = $ml_info[1];
				
				#####################################################
				# start at this number
				#
				# unlock? 		
				if($start_num_lock == 1){ 
					if($start_num == $start_num_count){ 
						$start_num_lock = 0; 
					}
					$start_num_count++;
				}
				next if($start_num_lock == 1);
			
				
				#####################################################
				# start at this email
				#
				# unlock? 
				if(( $start_email_lock == 1 ) && ( $mailing eq $start_email )){ 
						$start_email_lock = 0;
				}
				next if($start_email_lock==1);
				# keep count of how many people we have
				$mailing_count++; 
				# if we're sending using batching...	
				if($send_batch eq "1"){ 
					# this looks to see if we've filled our quota of 
					# sending for this batch, if it gets tripped, 
					# we'll sleep() for a few. 	
				if($n_people == $n_letters){   
					$stop_email = $mailing; 
					# if we're sending using batching...	
					#  yeah, I know repeats.. 
					if($send_batch eq "1"){			
						my ($sec, $min, $hour, $day, $month, $year) = (localtime)[0,1,2,3,4,5];
						my $sent_time = sprintf("Batch Completed: %02d/%02d/%02d %02d:%02d:%02d",  $month+1, $day, $year+1900, $hour, $min, $sec);			
						# lets tell these people we are done with this batch
						if($self->{list_info}->{get_batch_notification} eq "1"){ 		
							$self->_email_batch_notification(-fields       => \%fields, 
							  								 -batch_num    => $batch_num, 
															 -start_time   => $mail_start_time, 
															 -sent_time    => $sent_time, 
															 -emails_sent  => $n_people,
															 -last_email   => $stop_email);     
							 }
							 $log->mj_log($self->{list_info}->{list}, "Batch $batch_num Completed", "subject:$fields{Subject}, $mail_start_time, $sent_time, Last Email Sent To:$stop_email, Sleeping:$self->{list_info}->{bulk_sleep_amount}") if $LOG{mass_mailings};
				
					 }
		
					  # sleep a few, wake up!	
					  sleep($seconds);
					  # keep a count on how many batches we had. 
					  $batch_num++;
					  # and figure out where we are in this batch. 
					  $n_letters+=$letters;
					  $sleep_num++;
				  }
			}

			 # we've gotten to the actual mailing of the list message 
			 # zowee. 
			  
			 my $mailing_body = $send_body;
				# find the email, pin
				
				$mailing_body  = $self->_merge_fields(-body         => $mailing_body, 
								                      -merge_fields => \@ml_info);
											 
				$fields{Body}  = $mailing_body ;
				$fields{To}    = $mailing;
				# Debug Information, Always nice
				$fields{Debug} = {-Messages_Sent    => $n_people, 
								  -Last_Email       => $mailing,
								  -Message_Subject  => $fields{Subject},
								  -List_File        => $path_to_list,
								  -List_File_Size   => -s"$path_to_list"};
				# send, pretty easy, eh? 
				$self->send(%fields); 
				
				
				
				# keep count
				$n_people++; 
	
			}
		
			# if we be done, and if we be supposed to tell people, 
			my $notify = 0; 
			if(defined($self->{list_info}->{get_finished_notification})){ 
				$notify = $self->{list_info}->{get_finished_notification}; 
			}
			
			$mailing_amount = $mailing_count; 
			my ($dsec, $dmin, $dhour, $dday, $dmonth, $dyear) = (localtime)[0,1,2,3,4,5];
			my $mail_end_time = sprintf("Mailing Completed: %02d/%02d/%02d %02d:%02d:%02d",  $dmonth+1, $dday, $dyear+1900, $dhour, $dmin, $dsec);	
	
	
			if($notify  eq "1"){ 
				$self->_email_finished_notification(-fields       => \%fields, 
													-batch_num    => $batch_num, 
													-start_time   => $mail_start_time, 
													-end_time     => $mail_end_time, 
													-emails_sent  => $n_people,
													-last_email   => $stop_email); 
													
			}       
		
			$log->mj_log($self->{list_info}->{list}, 'List Mailing Completed', "subject:$fields{Subject}, $mail_start_time, $mail_end_time, Mailing Amount:$mailing_amount") if $LOG{mass_mailings};
		  
			if(($start_email_lock==1) && defined($start_email) && ($mailing_amount == 0)){ 
				$self->_mail_error_no_start_email(-fields      => {%fields}, 
												  -start_email => $start_email); 
			}
						
			if(($start_num_lock==1) && defined($start_num) && ($mailing_amount == 0)){ 
			$self->_mail_error_no_start_num(-fields      => {%fields}, 
											-start_num   => $start_num,
											-email_count => $start_num_count);
			}
		
			# last thing, get rid of the temp file we had the subscriber list in. 
			# nothing to it. 

			close(MAILLIST); 
			unlink($path_to_list) or 
			warn "$PROGRAM_NAME $VER error in Mail.pm Can't remove temporary list file: '$path_to_list' $!\n";
				
			if($self->{list_type} eq 'invitelist'){ 
				$lh->remove_this_listype(-Type=> 'invitelist');
			}
		
			 exit(0); 		 
		} elsif ($! =~ /No more process/) {     
			# EAGAIN, supposedly recoverable fork error
			sleep 5;
			redo FORK;
		} else {
			# weird fork error
			die "$PROGRAM_NAME $VER Error in Mail.pm, Unable to Fork new process to mass e-mail list message: $!\n";
			}
		}
	}
}




sub bulk_send_bulk_smtp { 
	my $self = shift; 
	my %args = (-fields => {},
				-list_file => undef,
				@_);	
	my $fields = $args{-fields};
	
	
	
	require Mail::Bulkmail; 
	my %bulk_send_headers; 
	foreach my $field (@default_headers){
		$bulk_send_headers{$field} = $fields->{$field} if defined $fields->{$field};
	}	
	
	$bulk_send_headers{'Content-Type'} .= '; charset='. $self->{list_info}->{charset_value} if(defined($self->{list_info}->{charset_value}) && (defined($bulk_send_headers{'Content-Type'})));
	
	
	
	
	$self->_pop_before_smtp;    
	$bulk_send_headers{"LIST"}           = $args{-list_file};
	$bulk_send_headers{"Message"}        = $fields->{Body};
	$bulk_send_headers{"Smtp"}           = $self->{list_info}->{smtp_server};
	$bulk_send_headers{'List-Software'} .= " SMTP Bulk";
	$bulk_send_headers{'ERRFILE'}        = $SMTP_ERROR_LOG if(defined($SMTP_ERROR_LOG) && ($SMTP_ERROR_LOG ne '')); 		
	
	$bulk_send_headers{'Return-Path'} = $self->{list_info}->{admin_email} if($self->{list_info}->{print_return_path_header} == 1);
	
	
	# lets do a quick convert here... 
	# this is for mail merging and stuff
	
	$bulk_send_headers{"Message"} =~ s/\[email\]/BULK_EMAIL/g; 
	$bulk_send_headers{"Message"} =~ s/\[pin\]/PIN/g; 
	$bulk_send_headers{"Message"} =~ s/\[mojo_url\]/$MOJO_URL/g; 
	
	my $bulk = Mail::Bulkmail->new(%bulk_send_headers) or die Mail::Bulkmail->error();
	$bulk->Trusting(1) if $SMTP_TRUSTING == 1; 
	
	
	
	$bulk->merge({BULK_MAILMERGE => 'BULK_EMAIL::PIN' . $self->_merge_fields_string()});
	
	if($FORK_SMTP_BULK_MAILINGS == 1){ 
		my $pid;
		FORK: {
			if ($pid = fork) {
				# parent
				return 1;
			} elsif (defined $pid) { # $pid is zero here if defined
				# child   	
				$bulk->bulkmail() or die Mail::Bulkmail->error();
				$log->mj_log($bulk_send_headers{List}, 'Bulk Mail Sent', "subject:$bulk_send_headers{Subject}") if $LOG{mass_mailings};    
				unlink($args{-list_file}) or 
					warn "$PROGRAM_NAME $VER error in Mail.pm Can't remove temporary list file: '$args{-list_file}' $!\n";
				exit(0); 
			 }elsif($! =~ /No more process/) {     
				# EAGAIN, supposedly recoverable fork error
				sleep 5;
				redo FORK;
			}else{
				# weird fork error
				die "$PROGRAM_NAME $VER Error in Mail.pm, Unable to Fork new process to mass e-mail list message using Mail::Bulkmail: $!\n";
			}
		}
	}else{ 
		$bulk->bulkmail() or die Mail::Bulkmail->error();
		$log->mj_log($self->{list_info}->{list}, 'Bulk Mail Sent', "subject:$bulk_send_headers{Subject}") if $LOG{mass_mailings};    
		unlink($args{-list_file}) or 
		warn "$PROGRAM_NAME $VER error in Mail.pm Can't remove temporary list file: '$args{-list_file}' $!\n";
	}
}				 


sub _strip_fields { 
	my $self = shift; 
	my %fields = @_; 
	require Mail::Address;
	my $to_temp = (Mail::Address->parse($fields{To}))[0];
	   $fields{To} = $to_temp->address();  	
	my $from_temp = (Mail::Address->parse($fields{From}))[0];
	   $fields{From} = $from_temp -> address();  
	return %fields;

}



sub _make_general_headers { 
	my $self = shift; 
	my %gh; 
	if($self->{list_info}->{list}){ 
		# PHRASE, ADDRESS, [ COMMENT ]
		require Mail::Address;			
		my $From_obj = Mail::Address->new($self->{list_info}->{list_name}, $self->{list_info}->{mojo_email});
		$gh{From}       = $From_obj->format;
		$gh{'Reply-To'} = $From_obj->format;
			
		my $Errors_To_obj = Mail::Address->new(undef, $self->{list_info}->{admin_email});
		$gh{'Errors-To'}   = $Errors_To_obj->format;
		#$gh{'Return-Path'} = $Errors_To_obj->format;
	
		
	}
	return %gh;
}


sub _make_list_headers { 
	my $self = shift; 
	my %lh;
	if($self->{list_info}->{list}){ 
		if($self->{list_info}->{print_list_headers} != 0){ 	
			$lh{Organization}       =   $self->{list_info}->{list_name};
			$lh{List}               =   $self->{list_info}->{list};
			$lh{'List-URL'}         =   $MOJO_URL.'?l='.$self->{list_info}->{list};
			$lh{'List-Owner'}       =   '<'.$self->{list_info}->{mojo_email}.'>';
			$lh{'List-Unsubscribe'} =    unsubscribe_link(-list => $self->{list_info}->{list}); 
			$lh{'List-Subscribe'}   =    subscribe_link(  -list => $self->{list_info}->{list}, -function => 's');
			if($self->{list_info}->{show_archives} ne "0"){
				$lh{'List-Archive'} =  $MOJO_URL.'?f=archive&l='.$self->{list_info}->{list};   
			}
		}
	}
	return %lh;
}



sub _pop_before_smtp { 
	my $self = shift; 
	
	require MOJO::Password; 
	
	my %args =(-pop3_server         => $self->{list_info}->{pop3_server},
	           -pop3_username       => $self->{list_info}->{pop3_username},
	           -pop3_password       => MOJO::Password::cipher_decrypt($self->{list_info}->{cipher_key}, $self->{list_info}->{pop3_password}),
	           @_);          
	            		
	if(($self->{list_info}->{use_pop_before_smtp} == 1) &&
	   ($args{-pop3_server})                            &&
	   ($args{-pop3_username})                          &&
	   ($args{-pop3_password})){
		eval "require Net::POP3";			
		if(!$@){
			my $pop    = Net::POP3->new($args{-pop3_server}); 
			return undef if $pop eq undef;
			my $status = $pop->login($args{-pop3_username}, $args{-pop3_password}); 
			return $status;
		}else{ 
			warn("Cannot find Net::POP3, is it installed? - $!");
		}
	}
}


sub _email_batch_notification { 
	my $self = shift; 
	my %args = (-fields      => {}, 
				-batch_num    => undef, 
				-start_time  => undef, 
				-sent_time   => undef, 
				-emails_sent => undef,
				-last_email  => undef,
				@_);  
	my $fields = $args{-fields}; 
	
	
	my %status_mailing; 
		$status_mailing{To}            = $fields->{From};
		$status_mailing{From}          = $fields->{From};
		$status_mailing{List}          = $fields->{List};
		$status_mailing{Subject}        = "$fields->{List} Batch \#$args{-batch_num} Complete. - $fields->{Subject}"; 
	my $status_body;
	   $status_body  = "\nMailing Summary:\n";
	   $status_body .= '_' x 72;
	   $status_body .= "\n\nBatch number: $args{-batch_num} has been completed!\n"; 
	   $status_body .= "Your list mailing has reached: $args{-emails_sent} e-mail address(es)\n\n";
	   $status_body .= "$args{-start_time}\n";
	   $status_body .= "$args{-sent_time}\n\n"; 
	   $status_body .= "Last email of this batch was sent to: $args{-last_email}\n\n";
	   $status_body .= "Waiting $self->{list_info}->{bulk_sleep_amount} second(s) until next batch,\n";
	   $status_body .= "\n          -$PROGRAM_NAME\n\n"; 
	   $status_mailing{Body} = $status_body;
	   $self->send(%status_mailing);	    
}



sub _email_finished_notification { 
	my $self = shift; 
	
	my %args = (-fields      => {}, 
			-batch_num    => undef, 
			-start_time  => undef, 
			-end_time   => undef, 
			-emails_sent => undef,
			-last_email  => undef,
			@_);  
	my $fields = $args{-fields}; 
	
	
	my %done_mailing; 
	$done_mailing{To}            = $fields->{From};
	$done_mailing{From}          = $fields->{From};
	$done_mailing{List}          = $fields->{List};
	$done_mailing{Subject}       = "$fields->{List} Mailing Complete. - $fields->{Subject}"; 	
	my $done_body = "Your List Mailing has been successful!\n";
	   $done_body .= '_' x 72; 
	   $done_body .= "\n\nYour mailing has reached: $args{-emails_sent} e-mail addresse(s)\n\n";
	   $done_body .= "$args{-start_time}\n";
	   $done_body .= "$args{-end_time}\n";
	   $done_body .= "Last Email sent to: $args{-last_email}\n"; 
	   $done_body .= "\nHere is a copy of the message:\n"; 
	   $done_body .= "----------------------------------------------\n"; 
	   $done_body .= $fields->{Body}; 
	   $done_body .= "----------------------------------------------\n";
	   $done_body .= "-$PROGRAM_NAME\n\n"; 
	   $done_mailing{Body} = $done_body;
	   $self->send(%done_mailing);	        
	   

}


sub _mail_error_no_start_email { 
	my $self = shift; 
	my %args = (-fields		 => {}, 
				-start_email => undef,
				@_); 
	my $fields = $args{-fields};			
	my $start_mail_body; 
	$start_mail_body .= "$PROGRAM_NAME Warning!\n\n";
	
	
	$start_mail_body .= "It appears that no email was sent during your last mailing, since the\n";
	$start_mail_body .= "email address you were looking to start at :\n\n";
	
	$start_mail_body .= "$args{-start_email}\n\n";
	
	
	$start_mail_body .= "was never found.  It's possible that this email address isn't\n";
	$start_mail_body .= "subscribed to your list. Please make sure you entered the\n";
	$start_mail_body .= "correct email address.\n";
	$start_mail_body .= "		-$PROGRAM_NAME\n";


	 my %start_mail_error = ( 
		From    => $fields->{From}, 
		To      => $fields->{From},                     
		List    => $fields->{List},
		Subject => "$fields->{List} - Possible Mailing Error, please read below:",
		Body    => $start_mail_body); 
		$self->send(%start_mail_error);
	
}				



sub _mail_error_no_start_num { 
	my $self = shift; 
	my %args = (-fields      => {},
				-start_num   => undef,
				-email_count => undef,
				@_); 
	my $fields = $args{-fields}; 
		
	my $start_mail_body;
	   $start_mail_body  = "$PROGRAM_NAME Warning!\n\n";
	   $start_mail_body .= "It appears that no email was sent during your last mailing, since mailing\n";
	   $start_mail_body .= "was supposed to being at email number: $args{-start_num} There are about: $args{email_count} email\n"; 
	   $start_mail_body .= "address(es) in your list. It's possible that you gave a number to start\n";
	   $start_mail_body .= "at that's larger than the list itself.";
	   $start_mail_body .= "	-$PROGRAM_NAME\n";

	 my %start_mail_error = ( 
		From    => $fields->{From}, 
		To      => $fields->{From},                     
		List    => $fields->{List},
		Subject => "$fields->{List} - Possible Mailing Error, please read below:",
		Body    => $start_mail_body); 
		$self->send(%start_mail_error);
		
					
}



sub _send_die { 
	
	my $self  = shift; 
	my $debug = shift; 
	my $report;
	
	if($debug){ 
		$report = "$PROGRAM_NAME $VER Mass Mailing Error! INFORMATION: Messages Sent: $debug->{-Messages_Sent},  Mailing Failed At Address: $debug->{-Last_Email}, Message Subject: $debug->{-Message_Subject}, Using List File: $debug->{-List_File}, List File Size: $debug->{-List_File_Size} bytes, Details: $!";
		die($report); 
	}else{
		die("$PROGRAM_NAME $VER Error: can't pipe to mail program using settings: '$MAIL_SETTINGS': $!\n");
	}
	
}


sub _merge_fields_string { 
	my $self = shift; 
	
	my @merge_fields = split(',',  $self->{list_info}->{merge_fields}); 
	my $merge_fields; 
	
	foreach(@merge_fields){ 
		$merge_fields .= '::' . '\['.$_.'\]';
	}	
	return $merge_fields;
}

sub _merge_fields { 
	my $self = shift; 
	my %args = (-body         => undef, 
				-merge_fields => [],
				@_);

	$args{-body} =~ s/\[email\]/$args{-merge_fields}->[0]/g;				
	$args{-body} =~ s/\[pin\]/$args{-merge_fields}->[1]/g;

	my @merge_fields = split(',',  $self->{list_info}->{merge_fields});
	my $i;
	
	for($i=0;$i<=$#merge_fields;$i++){ 
		$args{-body} =~ s/\[$merge_fields[$i]\]/$args{-merge_fields}->[$i+2]/g;	
	}
	return $args{-body};
}




sub DESTROY { 

	# DESTROY ALL ASTROMEN!
	my $self = shift; 

}


1;


=pod

=head1 COPYRIGHT

Copyright (c) 1999 - 2002 Justin Simoni (justin@skazat.com) http://skazat.com 

All rights reserved. 

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

=head1 CONTACT INFO

Justin Simoni http://skazat.com justin\@skazat.com 

A good place to start to get information is at: 

http://mojo.skazat.com 


=cut
