The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Email::Send::SMTP::Gmail;

use strict;
use warnings;
use vars qw($VERSION);

$VERSION='0.88';
require Net::SMTPS;
require Net::SMTP;
use MIME::Base64;
use File::Spec;
use LWP::MediaTypes;
use Email::Date::Format qw(email_date);

sub new{
  my $class=shift;
  my $self={@_};
  bless($self, $class);
  my %properties=@_;
  my $smtp='smtp.gmail.com'; # Default value
  my $port='default'; # Default value
  my $layer='tls'; # Default value
  my $auth='LOGIN'; # Default
  my $ssl_verify_mode=''; #Default - Warning SSL_VERIFY_NONE
  my $timeout=60;

  $smtp=$properties{'-smtp'} if defined $properties{'-smtp'};
  $port=$properties{'-port'} if defined $properties{'-port'};
  $layer=$properties{'-layer'} if defined $properties{'-layer'};
  $auth=$properties{'-auth'} if defined $properties{'-auth'};
  $ssl_verify_mode=$properties{'-ssl_verify_mode'} if defined $properties{'-ssl_verify_mode'};
  $timeout=$properties{'-timeout'} if defined $properties{'-timeout'};

  if(defined $properties{'-from'}){
    $self->{from}=$properties{'-from'};
  }
  else{
    $self->{from}=$properties{'-login'};
  }

  my $connect=$self->_initsmtp($smtp,$port,$properties{'-login'},$properties{'-pass'},$layer,$auth,$properties{'-debug'},$ssl_verify_mode,$properties{'-ssl_verify_path'},$properties{'-$ssl_verify_ca'},$timeout);

  return -1,$self->{error} if(defined $self->{error});
#  return $connect if($connect==-1);
#
  return $self;
}

sub _initsmtp{
  my $self=shift;
  my $smtp=shift;
  my $port=shift;
  my $login=shift;
  my $pass=shift;
  my $layer=shift;
  my $auth=shift;
  my $debug=shift;
  my $ssl_mode=shift;
  my $ssl_path=shift;
  my $ssl_ca=shift;
  my $timeout=shift;

  # The module sets the SMTP google but could use another!
  # Set port if default
  if($port eq 'default'){
      if($layer eq 'ssl'){
          $port=465;
      }
      else{
          $port=25;
      }
  }
 print "Connecting to $smtp using $layer with $auth on port $port and timeout of $timeout\n" if $debug;
  # Set security layer from $layer
  if($layer eq 'none')
  {
    if (not $self->{sender} = Net::SMTP->new($smtp, Port =>$port, Debug=>$debug, Timeout=>$timeout)){
      my $error_string=$self->{sender}->message();
      chomp $error_string;
      $self->{error}=$error_string;
      print "Could not connect to SMTP server ($smtp $port)\n" if $debug;
      return $self;
      #return -1;
    }
  }
  else{
    my $sec=undef;
    if($layer eq 'tls'){$sec='starttls';}
    elsif($layer eq 'ssl'){$sec='ssl';}
    if (not $self->{sender} = Net::SMTPS->new($smtp, Port =>$port, doSSL=>$sec, Debug=>$debug, SSL_verify_mode=>$ssl_mode, SSL_ca_file=>$ssl_ca,SSL_ca_path=>$ssl_path, Timeout=>$timeout)){
      #my $error_string=$self->{sender}->message();
      #chomp $error_string;
      # $self->{error}=$error_string;
      $self->{error}=$@;
      print "Could not connect to SMTP server\n" if $debug;
      return $self;
      #return -1;
    }
  }
  if($auth ne 'none'){
     unless($self->{sender}->auth($login,$pass,$auth)){
         my $error_string=$self->{sender}->message();
         chomp $error_string;
         $self->{error}=$error_string;
         print "Authentication (SMTP) failed\n" if $debug;
         #return -1;
     }
  }
  return $self;
}

sub bye{
  my $self=shift;
  $self->{sender}->quit();
  return $self;
}

sub banner{
  my $self=shift;
  my $banner=$self->{sender}->banner();
  chomp $banner;
  return $banner;
}

sub _checkfiles
{
# Checks that all the attachments exist
  my $attachs=shift;
  my $verbose=shift;

  my $result=''; # list of valid attachments

  my @attachments=split(/,/,$attachs);
  foreach my $attach(@attachments)
  {
     $attach=~s/\A[\s,\0,\t,\n,\r]*//;
     $attach=~s/[\s,\0,\t,\n,\r]*\Z//;

     unless (-f $attach) {
       print "Unable to find the attachment file: $attach (removed from list)\n" if $verbose;
     }
     else{
       my $opened=open(my $file,'<',$attach);
       if( not $opened){
         print "Unable to open the attachment file: $attach (removed from list)\n" if $verbose;
       }
       else{
         close $file;
         $result.=','.$attach;
         print "Attachment file: $attach added\n" if $verbose;
       }
     }
  }
  $result=~s/\A\,//;
  return $result;
}

sub _checkfilelist
{
# Checks that all the attachments exist
  my $attachs=shift;
  my $verbose=shift;

  my $result=undef; # list of valid attachments
  my $i=0;

  foreach my $attach(@$attachs)
  {
     $attach->{file}=~s/\A[\s,\0,\t,\n,\r]*//;
     $attach->{file}=~s/[\s,\0,\t,\n,\r]*\Z//;

     unless (-f $attach->{file}) {
       print "Unable to find the attachment file: $attach->{file} (removed from list)\n" if $verbose;
     }
     else{
       my $opened=open(my $file,'<',$attach->{file});
       if( not $opened){
          print "Unable to open the attachment file: $attach->{file} (removed from list)\n" if $verbose;
       }
       else{
         close $file;
         $result->[$i]->{file}=$attach->{file};
         $i++;
         print "Attachment file: $attach->{file} added\n" if $verbose;
       }
     }
  }
  return $result;
}

sub _createboundary
{
# Create arbitrary frontier text used to separate different parts of the message
  return "This-is-a-mail-boundary-8217539";
}

sub send
{
  my $self=shift;
  my %properties=@_; # rest of params by hash

  my $verbose=0;
  $verbose=$properties{'-verbose'} if defined $properties{'-verbose'};
  # Load all the email param
  my $mail;

  $mail->{to}='';
  $mail->{to}=$properties{'-to'} if defined $properties{'-to'};
  if($mail->{to} eq ''){
      print "No RCPT found. Please add the TO field\n";
      $self->{error}='"No RCPT found. Please add the TO field';
      return -1,$self->{error};
  }

  $mail->{from}=$self->{from};
  $mail->{from}=$properties{'-from'} if defined $properties{'-from'};

  $mail->{replyto}=$mail->{from};
  $mail->{replyto}=$properties{'-replyto'} if defined $properties{'-replyto'};

  $mail->{cc}='';
  $mail->{cc}=$properties{'-cc'} if defined $properties{'-cc'};

  $mail->{bcc}='';
  $mail->{bcc}=$properties{'-bcc'} if defined $properties{'-bcc'};

  $mail->{charset}='UTF-8';
  $mail->{charset}=$properties{'-charset'} if defined $properties{'-charset'};

  $mail->{contenttype}='text/plain';
  $mail->{contenttype}=$properties{'-contenttype'} if defined $properties{'-contenttype'};

  $mail->{subject}='';
  $mail->{subject}=$properties{'-subject'} if defined $properties{'-subject'};

  $mail->{body}='';
  $mail->{body}=$properties{'-body'} if defined $properties{'-body'};

  $mail->{attachments}='';
  $mail->{attachments}=$properties{'-attachments'} if defined $properties{'-attachments'};

  $mail->{attachmentlist}=$properties{'-attachmentlist'} if defined $properties{'-attachmentlist'};

#  if(($mail->{attachments} ne '')and($self->_checkfiles($mail->{attachments})))
  if($mail->{attachments} ne '')
  {
      $mail->{attachments}=_checkfiles($mail->{attachments},$verbose);
      print "Attachments separated by comma successfully verified\n" if $verbose;
  }
#  if((defined $mail->{attachmentlist})and($self->_checkfilelist($mail->{attachmentlist}))){
  if(defined $mail->{attachmentlist}){
      $mail->{attachmentlist}=_checkfilelist($mail->{attachmentlist},$verbose);
      print "Attachments \@list successfully verified\n" if $verbose;
  }

  # eval{
      my $boundary=_createboundary();

      $self->{sender}->mail($mail->{from} . "\n");

      my @recepients = split(/,/, $mail->{to});
      foreach my $recp (@recepients) {
          $self->{sender}->to($recp . "\n");
      }
      my @ccrecepients = split(/,/, $mail->{cc});
      foreach my $recp (@ccrecepients) {
          $self->{sender}->cc($recp . "\n");
      }
      my @bccrecepients = split(/,/, $mail->{bcc});
      foreach my $recp (@bccrecepients) {
          $self->{sender}->bcc($recp . "\n");
      }

      $self->{sender}->data();

      #Send header
      $self->{sender}->datasend("From: " . $mail->{from} . "\n");
      $self->{sender}->datasend("To: " . $mail->{to} . "\n");
      $self->{sender}->datasend("Cc: " . $mail->{cc} . "\n") if ($mail->{cc} ne '');
      $self->{sender}->datasend("Reply-To: " . $mail->{replyto} . "\n");
      $self->{sender}->datasend("Subject: " . $mail->{subject} . "\n");
      $self->{sender}->datasend("Date: " . email_date(). "\n");

      if($mail->{attachments} ne '')
      {
        print "With Attachments\n" if $verbose;
        $self->{sender}->datasend("MIME-Version: 1.0\n");
        $self->{sender}->datasend("Content-Type: multipart/mixed; BOUNDARY=\"$boundary\"\n");

        # Send text body
        $self->{sender}->datasend("\n--$boundary\n");
        $self->{sender}->datasend("Content-Type: ".$mail->{contenttype}."; charset=".$mail->{charset}."\n");

        $self->{sender}->datasend("\n");
        $self->{sender}->datasend($mail->{body} . "\n\n");

        my @attachments=split(/,/,$mail->{attachments});

        foreach my $attach(@attachments)
        {
           my($bytesread, $buffer, $data, $total);

           $attach=~s/\A[\s,\0,\t,\n,\r]*//;
           $attach=~s/[\s,\0,\t,\n,\r]*\Z//;

           my $opened=open(my $file,'<',$attach);
           binmode($file);
           while (($bytesread = sysread($file, $buffer, 1024)) == 1024) {
             $total += $bytesread;
             $data .= $buffer;
           }
           if ($bytesread) {
              $data .= $buffer;
              $total += $bytesread;
           }
           close $file;
           # Get the file name without its directory
           my ($volume, $dir, $fileName) = File::Spec->splitpath($attach);
           # Get the MIME type
           my $contentType = guess_media_type($attach);
           print "Composing MIME with attach $attach\n" if $verbose;
           if ($data) {
              $self->{sender}->datasend("--$boundary\n");
              $self->{sender}->datasend("Content-Type: $contentType; name=\"$fileName\"\n");
              $self->{sender}->datasend("Content-Transfer-Encoding: base64\n");
              $self->{sender}->datasend("Content-Disposition: attachment; =filename=\"$fileName\"\n\n");
              $self->{sender}->datasend(encode_base64($data));
              $self->{sender}->datasend("--$boundary\n");
           }
          }
          $self->{sender}->datasend("\n--$boundary--\n"); # send endboundary end message
      }

      elsif(defined $mail->{attachmentlist})
      {
        print "With Attachments\n" if $verbose;
        $self->{sender}->datasend("MIME-Version: 1.0\n");
        $self->{sender}->datasend("Content-Type: multipart/mixed; BOUNDARY=\"$boundary\"\n");

        # Send text body
        $self->{sender}->datasend("\n--$boundary\n");
        $self->{sender}->datasend("Content-Type: ".$mail->{contenttype}."; charset=".$mail->{charset}."\n");

        $self->{sender}->datasend("\n");
        $self->{sender}->datasend($mail->{body} . "\n\n");

        my $attachments=$mail->{attachmentlist};
        foreach my $attach(@$attachments)
        {
           my($bytesread, $buffer, $data, $total);

           $attach->{file}=~s/\A[\s,\0,\t,\n,\r]*//;
           $attach->{file}=~s/[\s,\0,\t,\n,\r]*\Z//;

           my $opened=open(my $file,'<',$attach->{file});
           binmode($file);
           while (($bytesread = sysread($file, $buffer, 1024)) == 1024) {
             $total += $bytesread;
             $data .= $buffer;
           }
           if ($bytesread) {
              $data .= $buffer;
              $total += $bytesread;
           }
           close $file;
           # Get the file name without its directory
           my ($volume, $dir, $fileName) = File::Spec->splitpath($attach->{file});
           # Get the MIME type
           my $contentType = guess_media_type($attach->{file});
           print "Composing MIME with attach $attach->{file}\n" if $verbose;
           if ($data) {
              $self->{sender}->datasend("--$boundary\n");
              $self->{sender}->datasend("Content-Type: $contentType; name=\"$fileName\"\n");
              $self->{sender}->datasend("Content-Transfer-Encoding: base64\n");
              $self->{sender}->datasend("Content-Disposition: attachment; =filename=\"$fileName\"\n\n");
              $self->{sender}->datasend(encode_base64($data));
              $self->{sender}->datasend("--$boundary\n");
           }
          }
          $self->{sender}->datasend("\n--$boundary--\n"); # send endboundary end message
      }

      else { # No attachment
        print "With No attachments\n" if $verbose;
        # Send text body
        $self->{sender}->datasend("MIME-Version: 1.0\n");
        $self->{sender}->datasend("Content-Type: ".$mail->{contenttype}."; charset=".$mail->{charset}."\n");

        $self->{sender}->datasend("\n");
        $self->{sender}->datasend($mail->{body}."\n\n");

      }

      $self->{sender}->datasend("\n");

      if($self->{sender}->dataend()) {
          print "Email sent\n" if $verbose;
          return 1;
      }
      else{
          my $error_string=$self->{sender}->message();
          chomp $error_string;
          $self->{error}=$error_string;

          print "Sorry, there was an error during sending. Please, retry or use Debug\n" if $verbose;
          return -1,$self->{error};
      }

}

1;
__END__

=head1 NAME

Email::Send::SMTP::Gmail - Sends emails with attachments supporting Auth over TLS or SSL (for example: Google's SMTP).

=head1 SYNOPSIS

   use strict;
   use warnings;

   use Email::Send::SMTP::Gmail;

   my ($mail,$error)=Email::Send::SMTP::Gmail->new( -smtp=>'smtp.gmail.com',
                                                    -login=>'whateveraddress@gmail.com',
                                                    -pass=>'whatever_pass');

   print "session error: $error" unless ($email!=-1);

   $mail->send(-to=>'target@xxx.com', -subject=>'Hello!', -body=>'Just testing it',
               -attachments=>'full_path_to_file');

   $mail->bye;

=head1 DESCRIPTION

Simple module to send emails through Google's SMTP with or without attachments. Also supports regular Servers (with plain or none auth).
Works with regular Gmail accounts as with Google Apps (your own domains).
It supports basic functions such as CC, BCC, ReplyTo.

=over

=item new(-login=>'', -pass=>'' [,-smtp=>'',layer=>'',-port=>'',-debug=>''])

It creates the object and opens a session with the SMTP.

=over

=item I<smtp>: defines SMTP server. Default value: smtp.gmail.com

=item I<layer>: defines the secure layer to use. It could be 'tls', 'ssl' or 'none'. Default value: tls

=item I<port>: defines the port to use. Default values are 25 for tls and 465 for ssl

=item I<timeout>: defined Timeout for the connection. Default is 60 secs

=item I<auth>: defines the authentication method: ANONYMOUS, CRAM-MD5, DIGEST-MD5, EXTERNAL, GSSAPI, LOGIN (default) and PLAIN. It's currently based on SASL::Perl module

=item I<debug>: see the log information

Also supports SSL parameters as:

=item I<ssl_verify_mode>: SSL_VERIFY_NONE | SSL_VERIFY_PEER

=item I<ssl_verify_path>: SSL_ca_path if SSL_VERIFY_PEER

=item I<ssl_verify_file>: SSL_ca_file if SSL_VERIFY_PEER



=back

=item send(-from=>'', -to=>'', [-subject=>'', -cc=>'', -bcc=>'', -replyto=>'', -charset=>'', -body=>'', -attachments=>'', -verbose=>'1'])

It composes and sends the email in one shot

=over

=item I<to, cc, bcc>: comma separated email addresses

=item I<contenttype>: Content-Type for the body message. Examples are: text/plain (default), text/html, etc.

=item I<attachments>: comma separated files with full path

=item I<attachmentslist>: hashref $list, in format $list->[x]->{name} of files with full path. Example: $list->[0]->{file}='/full_path/file.pdf'


=back

=item bye

Closes the SMTP session

=back

=over

=item banner

Returns SMTP banner

=back

=head1 Examples

Examples

=over

Send email composed in HTML using Gmail

      use strict;
      use warnings;
      use Email::Send::SMTP::Gmail;
      my ($mail,$error)=Email::Send::SMTP::Gmail->new( -smtp=>'smtp.gmail.com',
                                                       -login=>'whateveraddress@gmail.com',
                                                       -pass=>'whatever_pass');

      print "session error: $error" unless ($email!=-1);

      $mail->send(-to=>'target@xxx.com', -subject=>'Hello!',
                  -body=>'Just testing it<br>Bye!',-contenttype=>'text/html');
      $mail->bye;

Send email using a SMTP server without secure layer and authentication

      use strict;
      use warnings;
      use Email::Send::SMTP::Gmail;
      my $mail=Email::Send::SMTP::Gmail->new( -smtp=>'my.smtp.server',-layer=>'none', -auth=>'none');

      $mail->send(-from=>'sender@yyy.com', -to=>'target@xxx.com', -subject=>'Hello!',
                  -body=>'Quick email');
      $mail->bye;

Send email with attachments in comma separated format

      use strict;
      use warnings;
      use Email::Send::SMTP::Gmail;
      my $mail=Email::Send::SMTP::Gmail->new( -smtp=>'smtp.gmail.com',
                                              -login=>'whateveraddress@gmail.com',
                                              -pass=>'whatever_pass');

      $mail->send(-to=>'target@xxx.com', -subject=>'Hello!',
                  -body=>'Just testing it<br>Bye!',-contenttype=>'text/html',
                  -attachments=>'/full_path/file1.pdf,/full_path/file2.pdf');
      $mail->bye;

Send email with attachments using hashref

      use strict;
      use warnings;
      use Email::Send::SMTP::Gmail;
      my $mail=Email::Send::SMTP::Gmail->new( -smtp=>'smtp.gmail.com',
                                              -login=>'whateveraddress@gmail.com',
                                              -pass=>'whatever_pass');

      my $att;
      $att->[0]->{file}='/full_path/file.pdf';
      $att->[1]->{file}='/full_path/file1.pdf';

      $mail->send(-to=>'target@xxx.com', -subject=>'Hello!',
                  -body=>'Just testing it<br>Bye!',-contenttype=>'text/html',
                  -attachmentlist=>$att);
      $mail->bye;

=back

=head1 BUGS

Please report any bugs or feature requests to C<bug-email-send-smtp-gmail at rt.cpan.org> or through the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Email-Send-SMTP-Gmail>.
You will automatically be notified of the progress on your bug as we make the changes.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Email::Send::SMTP::Gmail

You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Email-Send-SMTP-Gmail>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/Email-Send-SMTP-Gmail>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/Email-Send-SMTP-Gmail>

=item * Search CPAN

L<http://search.cpan.org/dist/Email-Send-SMTP-Gmail/>

=item * Repository

L<http://github.com/NoAuth/Bugs.html?Dist=Email-Send-SMTP-Gmail>

=back

=head1 AUTHORS

Juan Jose 'Peco' San Martin, C<< <peco at cpan.org> >>

Martin Vukovic, C<< <mvukovic at microbotica.es> >>

Flaviano Tresoldi, C<< <info at swwork.it> >>

Narcyz Knap, C<< <narcyz at gumed.edu.pl> >>

=head1 COPYRIGHT

Copyright 2014 Microbotica

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

=cut