File Coverage

blib/lib/Email/AutoReply.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             package Email::AutoReply;
2             our $rcsid = '$Id: AutoReply.pm 3014 2008-06-09 21:59:36Z adam $';
3              
4 1     1   30285 use strict;
  1         2  
  1         43  
5 1     1   5 use warnings;
  1         1  
  1         51  
6              
7             our $VERSION = '1.04';
8              
9             =head1 NAME
10              
11             Email::AutoReply - Perl extension for writing email autoresponders
12              
13             =head1 SYNOPSIS
14              
15             use Email::AutoReply;
16             my $auto = Email::AutoReply->new;
17             $auto->reply;
18              
19             =head1 DESCRIPTION
20              
21             This module may be useful in writing autoresponders. The example code above
22             will try to respond (using Sendmail) to an email message given as standard
23             input.
24              
25             The module will reply once to each email address it sees, storing
26             sent-to addresses in a database. This database class is
27             Email::AutoReply::DB::BerkeleyDB by default, but any class that
28             implements L may be used.
29              
30             =cut
31              
32 1     1   945 use Spiffy '-Base';
  1         5749  
  1         6  
33 1     1   4334  
  1     1   2  
  1         26  
  1         4  
  1         2  
  1         29  
34 1     1   4 use Carp qw(confess);
  1         2  
  1         39  
35 1     1   864 use Email::Address;
  1         33596  
  1         82  
36 1     1   677 use Email::AutoReply::DB::BerkeleyDB;
  0            
  0            
37             use Email::Send ();
38             use Email::Simple;
39             use File::Path ();
40             use Mail::ListDetector;
41              
42             =head2 ATTRIBUTES
43              
44             All attributes are set and get using code similar to the following:
45              
46             $auto = new Email::AutoReply;
47              
48             # get debug status
49             $dbg = $auto->debug;
50              
51             # set debug status to "on"
52             $auto->debug(1);
53              
54             =over 4
55              
56             =item B
57              
58             Set/get the class to use for the cache DB.
59              
60             Default: 'Email::AutoReply::DB::BerkeleyDB'
61              
62             =cut
63              
64             field 'cachedb_type' => 'Email::AutoReply::DB::BerkeleyDB';
65              
66             =item B
67              
68             Set/get weather debugging is enabled. 0 means off, 1 means on.
69              
70             Default: 0
71              
72             =cut
73              
74             field 'debug' => 0;
75              
76             =item B
77              
78             Set/get autoreply 'From' email for the autoreply. Example: 'adam@example.com'.
79              
80             =cut
81              
82             field 'from_email';
83              
84             =item B
85              
86             Set/get autoreply 'From' name for the autoreply. Example: 'Adam Monsen'.
87             Note: this will be ignored unless from_email is also set.
88              
89             Default: undef
90              
91             =cut
92              
93             field 'from_realname';
94              
95             =item B
96              
97             Set/get the hostname where this package will be executed. This is used
98             when constructing an X-Mail-AutoReply header for the autoreply.
99              
100             Default: 'localhost'
101              
102             =cut
103              
104             field 'hostname' => 'localhost';
105              
106             =item B
107              
108             Set/get the full text of the email to parse and reply to.
109              
110             Default: undef
111              
112             =cut
113              
114             field 'input_email';
115              
116             =item B
117              
118             Set/get the string which will serve as the body of the autoreply.
119              
120             Default: 'Sorry, the person you're trying to reach is unavailable.
121             This is an automated response from Email::AutoReply. See
122             http://search.cpan.org/perldoc?Email::AutoReply for more info.'
123              
124             =cut
125              
126             field response_text => <<'AutomatedResponse';
127             Sorry, the person you're trying to reach is unavailable.
128             This is an automated response from Email::AutoReply. See
129             http://search.cpan.org/perldoc?Email::AutoReply for more info.
130             AutomatedResponse
131              
132             =item B
133              
134             Set/get the directory to in which to store Email::AutoReply settings.
135              
136             Default: /home/$ENV{HOME}/.email-autoreply
137              
138             =cut
139              
140             field 'settings_dir' => "$ENV{HOME}/.email-autoreply";
141              
142             =item B
143              
144             Set/get the Email::Send class used to send the autoreply.
145              
146             Default: 'Sendmail'
147              
148             =cut
149              
150             field 'send_method' => 'Sendmail';
151              
152             =item B
153              
154             Set/get extra arguments passed to Email::Send::send(). By default, this is
155             '"-f $bot_from"', and this string is eval()'d. Quotes are significant! This is
156             double quotes inside of single quotes. $bot_from will expand to be either
157             from_email, or the name specfied in the To: field of the original email (if
158             from_email is unset). '"-f $bot_from"' is sendmail-specific, by the way, and
159             basically tells Sendmail to set the envelope sender to something different
160             than the default. See sendmail(8) for more details.
161              
162             Default: '"-f $bot_from"'
163              
164             =cut
165              
166             field 'send_method_args' => '"-f $bot_from"';
167              
168             =item B
169              
170             Set/get the subject to be used in the autoreply.
171              
172             Default: 'Out Of Office Automated Response'
173              
174             =cut
175              
176             field 'subject' => 'Out Of Office Automated Response';
177              
178             ### private fields
179             field '_cache_db';
180              
181             =back
182              
183             =head2 METHODS
184              
185             =over 4
186              
187             =item B
188              
189             Takes any attributes as arguments, or none:
190              
191             # set the debug and response_text attributes
192             my $auto = Email::AutoReply->new(
193             debug => 1, response_text => "I'm on vacation, ttyl."
194             );
195              
196             # no arguments
197             my $auto = Email::AutoReply->new;
198              
199             Returns a new Email::AutoReply object.
200              
201             =cut
202              
203             sub new {
204             $self = super;
205             $self->_create_settings_dir();
206             $self->_init_db();
207             return $self;
208             }
209              
210             sub _create_settings_dir {
211             my $dir = $self->settings_dir;
212             return if -d $dir;
213             warn "making $dir" if $self->debug;
214             eval {
215             File::Path::mkpath($dir);
216             };
217             confess $@ if $@;
218             }
219              
220             sub _init_db {
221             my $db_class = $self->cachedb_type();
222             my $db = $db_class->new(
223             email_autoreply_settings_dir => $self->settings_dir()
224             );
225             $self->_cache_db($db);
226             }
227              
228             sub _create_autoreply_from_address {
229             my %args = (input_to => undef, @_);
230             ref $args{input_to} eq 'Email::Address'
231             or confess 'input_to must be an Email::Address object';
232             my $rv;
233             if ($self->from_email) {
234             my $name = $self->from_realname || undef;
235             $rv = Email::Address->new($name => $self->from_email);
236             } else {
237             $rv = $args{input_to};
238             }
239             return $rv;
240             }
241              
242             =item B
243              
244             Takes no arguments.
245              
246             Returns a list of emails in the "already sent to" database.
247              
248             =cut
249              
250             sub dbdump {
251             return $self->_cache_db->fetch_all;
252             }
253              
254             =item B
255              
256             Takes no arguments. If the 'input_email' attribute is set, this class
257             will read that as the email to (possibly) autoreply to. If the
258             'input_email' attribute is not set, an email message will be extracted
259             from standard input.
260              
261             No return value.
262              
263             =cut
264              
265             sub reply {
266             my $input = $self->input_email;
267             if (!$input) {
268             local $/;
269             $input = ;
270             }
271             my $mail = new Email::Simple($input);
272             my ($from) = Email::Address->parse($mail->header("From"));
273             confess "couldn't parse a From address" if not $from;
274             my ($from_address) = lc($from->address);
275             my ($to) = Email::Address->parse($mail->header("To"));
276             confess "couldn't parse a To address" if not $to;
277              
278             if (not $self->in_cache(email=>$from_address) and
279             not $self->noreply_sender(email=>$from_address) and
280             not $self->is_maillist_msg(mailobj=>$mail) and
281             not $self->we_touched_it(mailobj=>$mail)) {
282              
283             my $bot_from_obj = $self->_create_autoreply_from_address(input_to => $to);
284             my $bot_from = $bot_from_obj->address;
285             my $bot_from_formatted = $bot_from_obj->format;
286              
287             my $autoreply_hdr =
288             "version=$VERSION,host=" . $self->hostname . ",from=".$bot_from;
289             my $reply = Email::Simple->new(''); # init w/empty string or it complains
290             warn "sending autoreply to $from_address from $bot_from" if $self->debug;
291              
292             $reply->header_set('Subject', $self->subject);
293             $reply->header_set('From', $bot_from_formatted);
294             $reply->header_set('To', $from->format);
295             $reply->header_set('X-Mail-AutoReply', $autoreply_hdr);
296             $reply->body_set($self->response_text);
297              
298             my $send_method_args = eval($self->send_method_args);
299             die $@ if $@;
300             Email::Send::send($self->send_method, $reply, $send_method_args);
301              
302             # cache the email address we just sent to
303             # XXX what if email sending failed?
304             my $recipient = Email::AutoReply::Recipient->new(
305             email => $from_address, timestamp => time,
306             );
307             $self->_cache_db->store($recipient);
308            
309             # we replied, so keep track.
310             # XXX doesn't matter unless we save this, so we should do that...
311             $mail->header_set('X-Mail-AutoReply', $autoreply_hdr);
312             } else {
313             warn "NOT SENDING" if $self->debug;
314             }
315             }
316              
317             sub in_cache {
318             my %args = (email => undef, @_);
319             my $found = $self->_cache_db->fetch($args{email}) ? 1 : 0;
320             warn "$args{email} in cache? ... '$found' " if $self->debug;
321             return $found ? 1 : 0;
322             }
323              
324             sub noreply_sender {
325             my %args = (email => undef, @_);
326              
327             my @patterns = (
328             qr/bounces.*@/,
329             qr/subscribe.*@/,
330             qr/noreply.*@/,
331             qr/mailer-daemon@/,
332             );
333              
334             for (@patterns) {
335             return 1 if $args{email} =~ m/$_/i;
336             }
337              
338             return 0;
339             }
340              
341             sub is_maillist_msg {
342             my %args = (mailobj => undef, @_);
343             ref $args{mailobj} eq 'Email::Simple'
344             or confess 'mailobj must be an Email::Simple object';
345             defined($args{mailobj}) or confess "Must pass in mailobj";
346             my $listobj = Mail::ListDetector->new($args{mailobj});
347             warn "Is this a mailing list message? ".defined($listobj) if $self->debug;
348             return defined $listobj;
349             }
350              
351             sub we_touched_it {
352             my %args = (mailobj => undef, @_);
353             ref $args{mailobj} eq 'Email::Simple'
354             or confess 'mailobj must be an Email::Simple object';
355             defined($args{mailobj}) or confess 'Must pass in mailobj';
356             return $args{mailobj}->header('X-Mail-AutoReply');
357             }
358              
359             1;
360              
361             __END__