NAME

Mojo::SMTP::Client - non-blocking SMTP client based on Mojo::IOLoop

SYNOPSIS

    # blocking
    my $smtp = Mojo::SMTP::Client->new(address => '10.54.17.28', autodie => 1);
    $smtp->send(
    	from => 'me@from.org',
    	to => 'you@to.org',
    	data => join("\r\n", 'From: me@from.org',
    	                     'To: you@to.org',
    	                     'Subject: Hello world!',
    	                     '',
    	                     'This is my first message!'
    	        ),
    	quit => 1
    );
    warn "Sent successfully"; # else will throw exception because of `autodie'
    # non-blocking
    my $smtp = Mojo::SMTP::Client->new(address => '10.54.17.28');
    $smtp->send(
    	from => 'me@from.org',
    	to => 'you@to.org',
    	data => join("\r\n", 'From: me@from.org',
    	                     'To: you@to.org',
    	                     'Subject: Hello world!',
    	                     '',
    	                     'This is my first message!'
                ),
    	quit => 1,
    	sub {
    		my ($smtp, $resp) = @_;
    		warn $resp->error ? 'Failed to send: '.$resp->error : 'Sent successfully';
    		Mojo::IOLoop->stop;
    	}
    );
    
    Mojo::IOLoop->start;

DESCRIPTION

With Mojo::SMTP::Client you can easily send emails from your Mojolicious application without blocking of Mojo::IOLoop.

EVENTS

Mojo::SMTP::Client inherits all events from Mojo::EventEmitter and can emit the following new ones

start

$smtp->on(start => sub {
	my ($smtp) = @_;
	# some servers delays first response to prevent SPAM
	$smtp->inactivity_timeout(5*60);
});

Emitted whenever a new connection is about to start. You can interrupt sending by dying or throwing an exception from this callback, error attribute of the response will contain corresponding error.

response

$smtp->on(response => sub {
	my ($smtp, $cmd, $resp) = @_;
	if ($cmd == Mojo::SMTP::Client::CMD_CONNECT) {
		# and after first response others should be fast enough
		$smtp->inactivity_timeout(10);
	}
});

Emitted for each SMTP response from the server. $cmd is a command constant for which this response was sent. $resp is Mojo::SMTP::Client::Response object. You can interrupt sending by dying or throwing an exception from this callback, error attribute of the response will contain corresponding error.

ATTRIBUTES

Mojo::SMTP::Client implements the following attributes, which you can set in the constructor or get/set later with object method call

address

Address of SMTP server (ip or domain name). Default is localhost

port

Port of SMTP server. Default is 25 for plain connection and 465 if TLS is enabled.

tls

Enable TLS. Should be true if SMTP server expects encrypted connection. Default is false. Proper version of IO::Socket::SSL should be installed for TLS support in Mojo::IOLoop::Client, which you can find with mojo version command.

tls_ca

Path to TLS certificate authority file. Also activates hostname verification.

tls_cert

Path to the TLS certificate file.

tls_key

Path to the TLS key file.

tls_verify

TLS verification mode. Use 0 to disable verification, which turned on by default.

hello

SMTP requires that you identify yourself. This option specifies a string to pass as your mail domain. Default is localhost.localdomain

connect_timeout

Maximum amount of time in seconds establishing a connection may take before getting canceled, defaults to the value of the MOJO_CONNECT_TIMEOUT environment variable or 10

inactivity_timeout

Maximum amount of time in seconds a connection can be inactive before getting closed, defaults to the value of the MOJO_INACTIVITY_TIMEOUT environment variable or 20. Setting the value to 0 will allow connections to be inactive indefinitely

ioloop

Event loop object to use for blocking I/O operations, defaults to a Mojo::IOLoop object

autodie

Defines should or not Mojo::SMTP::Client throw exceptions for any type of errors. This only usable for blocking usage of Mojo::SMTP::Client, because non-blocking one should never die. Throwed exception will be one of the specified in Mojo::SMTP::Client::Exception. When autodie attribute has false value you should check $resp>error yourself. Default is false.

METHODS

Mojo::SMTP::Client inherits all methods from Mojo::EventEmitter and implements the following new ones

send

$smtp->send(
	from => $mail_from,
	to   => $rcpt_to,
	data => $data,
	quit => 1,
	$nonblocking ? $cb : ()
);

Send specified commands to SMTP server. Arguments should be key => value pairs where key is a command and value is a value for this command. send understands the following commands:

hello

Send greeting to the server. Argument to this command should contain your domain name. Keep in mind, that Mojo::SMTP::Client will automatically send greeting to the server right after connection if you not specified hello as first command for send. Mojo::SMTP::Client first tries EHLO command for greeting and if server doesn't accept it Mojo::SMTP::Client retries with HELO command.

$smtp->send(hello => 'mymail.me');
starttls

Upgrades connection from plain to encrypted. Some servers requires this before sending any other commands. IO::Socket::SSL 0.98+ should be installed for this to work. See also "tls_ca", "tls_cert", "tls_key" attributes

$smtp->tls_ca('/etc/ssl/certs/ca-certificates.crt');
$smtp->send(starttls => 1);
auth

Authorize on SMTP server. Argument to this command should be a reference to a hash with type, login and password keys. Only PLAIN and LOGIN authorization are supported as type for now. You should authorize only once per session.

$smtp->send(auth => {login => 'oleg', password => 'qwerty'});      # defaults to AUTH PLAIN
$smtp->send(auth => {login => 'oleg', password => 'qwerty', type => 'login'}); # AUTH LOGIN
from

From which email this message was sent. Value for this cammand should be a string with email

$smtp->send(from => 'root@cpan.org');
to

To which email(s) this message should be sent. Value for this cammand should be a string with email or reference to array with email strings (for more than one recipient)

$smtp->send(to => 'oleg@cpan.org');
$smtp->send(to => ['oleg@cpan.org', 'do_not_reply@cpantesters.org']);
reset

After this command server should forget about any started mail transaction and reset it status as it was after response to EHLO/HELO. Note: transaction considered started after MAIL FROM (from) command.

$smtp->send(reset => 1);
data

Email body to be sent. Value for this command should be a string (or reference to a string) with email body or reference to subroutine each call of which should return some chunk of the email as string (or reference to a string) and empty string (or reference to empty string) at the end (useful to send big emails in memory-efficient way)

$smtp->send(data => "Subject: This is my first message\r\n\r\nSent from Mojolicious app");
$smtp->send(data => sub { sysread(DATA, my $buf, 1024); $buf });
quit

Send QUIT command to SMTP server which will close the connection. So for the next use of this server connection will be reestablished. If you want to send several emails with this server it will be more efficient to not quit the connection until last email will be sent.

For non-blocking usage last argument to send should be reference to subroutine which will be called when result will be available. Subroutine arguments will be ($smtp, $resp). Where $resp is object of Mojo::SMTP::Client::Response class. First you should check $resp->error - if it has true value this means that it was error somewhere while sending. If error has false value you can get code and message for response to last command with $resp->code (number) and $resp->message (string).

For blocking usage $resp will be returned as result of $smtp->send call. $resp is the same as for non-blocking result. If "autodie" attribute has true value send will throw an exception on any error. Which will be one of Mojo::SMTP::Client::Exception::* or an error throwed by the user inside event handler.

Note. For SMTP protocol it is important to send commands in certain order. Also send will send all commands in order you are specified. So, it is important to pass arguments to send in right order. For basic usage this will always be: from -> to -> data -> quit. You should also know that it is absolutely correct to specify several non-unique commands. For example you can send several emails with one send call:

$smtp->send(
	from => 'someone@somewhere.com',
	to   => 'somebody@somewhere.net',
	data => $mail_1,
	from => 'frodo@somewhere.com',
	to   => 'garry@somewhere.net',
	data => $mail_2,
	quit => 1
);

Note. Connection to SMTP server will be made on first send or for each send when socket connection not already estabilished (was closed by QUIT command or errors in the stream). It is error to make several simultaneous non-blocking send calls on the same Mojo::SMTP::Client, because each client has one global stream per client. So, you need to create several clients to make simultaneous sending.

prepend_cmd

$smtp->prepend_cmd(reset => 1, starttls => 1);

Prepend specified commands to the queue, so the next command sent to the server will be the first you specified in prepend_cmd. You can prepend commands only when sending already in progress and there are commands in the queue. So, the most common place to call prepend_cmd is inside response event handler. For example this is how we can say "start SSL session if server supports it":

$smtp->on(response => sub {
	my ($smtp, $cmd, $resp) = @_;
	if ($cmd == Mojo::SMTP::Client::CMD_EHLO && $resp->message =~ /STARTTLS/i) {
		$smtp->prepend_cmd(starttls => 1);
	}
});
$smtp->send(
	from => $from,
	to   => $to,
	data => $data,
	quit => 1
);

prepend_cmd accepts same commands as "send".

CONSTANTS

Mojo::SMTP::Client has this non-importable constants

CMD_CONNECT  # client connected to SMTP server
CMD_EHLO     # client sent EHLO command
CMD_HELO     # client sent HELO command
CMD_STARTTLS # client sent STARTTLS command
CMD_AUTH     # client sent AUTH command
CMD_FROM     # client sent MAIL FROM command
CMD_TO       # client sent RCPT TO command
CMD_DATA     # client sent DATA command
CMD_DATA_END # client sent . command
CMD_RESET    # client sent RSET command
CMD_QUIT     # client sent QUIT command

VARIABLES

Mojo::SMTP::Client has this non-importable variables

%CMD

Get human readable command by it constant

print $Mojo::SMTP::Client::CMD{ Mojo::SMTP::Client::CMD_EHLO };

COOKBOOK

How to send simple ASCII message

ASCII message is simple enough, so you can generate it by hand

$smtp->send(
	from => 'me@home.org',
	to   => 'you@work.org',
	data => join(
		"\r\n",
		'MIME-Version: 1.0',
		'Subject: Subject of the message',
		'From: me@home.org',
		'To: you@work.org',
		'Content-Type: text/plain; charset=UTF-8',
		'',
		'Text of the message'
	)
);

However it is not recommended to generate emails by hand if you are not familar with MIME standard. For more convenient approaches see below.

How to send text message with possible non-ASCII characters

For more convinient way to generate emails we can use some email generators available on CPAN. MIME::Lite for example. With such modules we can get email as a string and send it with Mojo::SMTP::Client

use MIME::Lite;
use Encode qw(encode decode);

my $msg = MIME::Lite->new(
	Type    => 'text',
	From    => 'me@home.org',
	To      => 'you@work.org',
	Subject => encode('MIME-Header', decode('utf-8', '世界, 労働, 5月!')),
	Data    => 'Novosibirsk (Russian: Новосибирск; IPA: [nəvəsʲɪˈbʲirsk]) is the third most populous '.
	           'city in Russia after Moscow and St. Petersburg and the most populous city in Asian Russia'
);
$msg->attr('content-type.charset' => 'UTF-8');

$smtp->send(
	from => 'me@home.org',
	to   => 'you@work.org',
	data => $msg->as_string
);

How to send message with attachment

This is also simple with help of MIME::Lite

use MIME::Lite;

my $msg = MIME::Lite->new(
	Type    => 'multipart/mixed',
	From    => 'me@home.org',
	To      => 'you@work.org',
	Subject => 'statistic for 10.03.2015'
);
$msg->attach(Path => '/home/kate/stat/10032015.xlsx', Disposition => 'attachment', Type => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

$smtp->send(
	from => 'me@home.org',
	to   => 'you@work.org',
	data => $msg->as_string
);

How to send message with BIG attachment

It will be not cool to get message with 50 mb attachment into memory before sending. Fortunately with help of MIME::Lite and MIME::Lite::Generator we can generate our email by small portions. As you remember data command accepts subroutine reference as argument, so it will be super easy to send our big email in memory-efficient way

use MIME::Lite;

my $msg = MIME::Lite->new(
	Type    => 'multipart/mixed',
	From    => 'me@home.org',
	To      => 'you@work.org',
	Subject => 'my home video'
);
# Note: MIME::Lite will not load this file into memory
$msg->attach(Path => '/home/kate/videos/beach.avi', Disposition => 'attachment', Type => "video/msvideo");

my $generator = MIME::Lite::Generator->new($msg);

$smtp->send(
	from => 'me@home.org',
	to   => 'you@work.org',
	data => sub { $generator->get() }
);

How to send message using public email services like Gmail

Most such services provides access via SMTP in addition to web interface, but needs authorization. To protect your login and password most of them requires to start encrypted session (by upgrading plain connection with starttls or by initial tls connection). For example Gmail supports both this ways:

# make plain connection to port 25
my $smtp = Mojo::SMTP::Client->new(address => 'smtp.gmail.com');
# and upgrade it to TLS with starttls
$smtp->send(
	starttls => 1,
	auth => {login => $login, password => $password},
	from => $from,
	to   => $to,
	data => $msg,
	quit => 1
);

# or make initial TLS connection to port 465
my $smtp = Mojo::SMTP::Client->new(address => 'smtp.gmail.com', tls => 1);
# no need to use starttls
$smtp->send(
	auth => {login => $login, password => $password},
	from => $from,
	to   => $to,
	data => $msg,
	quit => 1
);

How to send message directly, without using of MTAs such as sendmail, postfix, exim, ...

Sometimes it is more suitable to send message directly to SMTP server of recipient. For example if you haven't any MTA available or want to check recipient's server responses (e.g. to know is such user exists on this server [see Mojo::Email::Checker::SMTP]). First you need to know address of necessary SMTP server. We'll get it with help of Net::DNS. Then we'll send it as usual

# will use non-blocking approach in this example
use strict;
use MIME::Lite;
use Net::DNS;
use Mojo::SMTP::Client;
use Mojo::IOLoop;

use constant TO => 'oleg@cpan.org';

my $loop = Mojo::IOLoop->singleton;
my $resolver = Net::DNS::Resolver->new();
my ($domain) = TO =~ /@(.+)/;

# Get MX records
my $sock = $resolver->bgsend($domain, 'MX');
$loop->reactor->io($sock => sub {
	my $packet = $resolver->bgread($sock);
	$loop->reactor->remove($sock);
	
	my @mx;
	if ($packet) {
		for my $rec ($packet->answer) {
			push @mx, $rec->exchange if $rec->type eq 'MX';
		}
	}
	
	# Will try with first or plain domain name if no mx records found
	my $address = @mx ? $mx[0] : $domain;
	
	my $smtp = Mojo::SMTP::Client->new(
		address => $address,
		# it is important to properly identify yourself
		hello   => 'home.org'
	);
	
	my $msg = MIME::Lite->new(
		Type    => 'text',
		From    => 'me@home.org',
		To      => TO,
		Subject => 'Direct email',
		Data    => 'Get it!'
	);
	
	$smtp->on(response => sub {
		# some debug
		my ($smtp, $cmd, $resp) = @_;
		
		print ">>", $Mojo::SMTP::Client::CMD{$cmd}, "\n";
		print "<<", $resp, "\n";
	});
	
	$smtp->send(
		from => 'me@home.org',
		to   => TO,
		data => $msg->as_string,
		quit => 1,
		sub {
			my ($smtp, $resp) = @_;
			
			warn $resp->error ? 'Failed to send: '.$resp->error :
			                      'Sent successfully with code: ', $resp->code;
			
			$loop->stop;
		}
	);
});
$loop->reactor->watch($sock, 1, 0);

$loop->start;

Note: some servers may check your PTR record, availability of SMTP server on your domain and so on.

SEE ALSO

Mojo::SMTP::Client::Response, Mojo::SMTP::Client::Exception, Mojolicious, Mojo::IOLoop, RFC5321 (SMTP), RFC3207 (STARTTLS), RFC4616 (AUTH PLAIN)

COPYRIGHT

Copyright Oleg G <oleg@cpan.org>.

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