Hello community!

Here you have my recent project using Mail::IMAPClient intended to manage some of my incoming emails. It is able to speak too, but the current speaking ability is provided by Win32::SAPI5 so if you want to use on other platforms, just modify the small voice sub at the end of the program (and lines 8 and 22).

The core of this simple client is the infinite while (1) loop at line 135: an incoming message will be passed to process_message (line 164).

Here in the process_message sub I put some simple example of managing emails: extract the sender (both address and display name), subject and body and some IMAP operation: mark as read, move the message.. modify to your needs.

On windows and using the SAPI voice this program will use the system default voice: you can modify it under control panel -> Speech recognition -> properties (or somthing like that.. you can figure it).

The program handles two signals: BREAK to show the current status of the program and, more important, INT to premit the IMAP logout and so a clean exit.

Here a small example of session:

shell>perl imap-monitorV5PM.pl -u user@example.com -s email.example.co +m -p 993 -ssl 1 -i 5 Enter password for user@example.com on email.example.com VOICE: user@example.com succesfully authenticated on email.example.com + port 993. Checking INBOX for incoming messages every 5 seconds. CTRL-C to exit the program permitting the IMAP logout CTRL-BREAK to review the current status of the program Tabella codici attiva: 1252 + # I press CTRL-BREAK ====================================================================== imap-monitorV5PM.pl PID 5052 Mon Jan 30 12:33:45 2023 - email.example.com connected checked messages: 3 ====================================================================== + # a new message triggering default rule ====================================================================== Mon Jan 30 12:47:29 2023 ====================================================================== VOICE: Default rule. New message from: Johann Sebastian Bach. Subject: + Listen to my new album! ====================================================================== + # I press CTRL-C Logging out.. VOICE: IMAP logout.. Exiting..

..and obviously the code:

use strict; use warnings; use Mail::IMAPClient; use Term::ReadKey; use Getopt::Long; use Encode qw(decode encode); use Win32::SAPI5; use utf8; my $VERSION = 5; my $user; my $server; my $port; my $ssl = 0; my $imap_folder = "INBOX"; my $sleep = 60; my $debug = 0; my $substr = 500; # used to cut body_string my %seen; # take count of already seen messages my $voice = Win32::SAPI5::SpVoice->new(); $|++; GetOptions ( "u|user=s" => \$user, "s|server=s" => \$server, "p|porta=i" => \$port, "ssl=i" => \$ssl, "f|folder=s" => \$imap_folder, "i|interval=i" => \$sleep, "debug" => \$debug, "substring=i" => \$substr, ) or show_help(); sub show_help{ print <<EOH; $0 usage: $0 -user LOGIN -server SERVER -port N [ -ssl 0|1 -folder FOLDER +-interval SECONDI --substring --debug] -u -user username to authenticate on the given server (REQU +IRED) -s -server IMAP server to connect to (REQUIRED) -p -port port used by the server for IMAP connections (REQU +IRED) -ssl use or not SSL to connect (0 or 1 - default 1) -f -folder IMAP folder to check (default: "INBOX") -i -interval seconds between checks (default 60) -substring how many bytes of mail body to print on screen (de +fault 500) -d -debug extra debug output EOH exit; } # check of required arguments my %check = (user=>$user,server=>$server,port=>$port); foreach my $need ( keys %check ){ unless ( defined $check{$need} ){ warn "\nPlease provide the parameter: $need"; show_help(); } } # gently ask for password print "Enter password for $user on $server\n"; my $password; ReadMode('noecho'); $password = ReadLine(0); chomp $password; ReadMode 'normal'; # the IMAP client init my $imap = Mail::IMAPClient->new( Server => $server, User => $user, password => $password, Port => $port, Ssl=> $ssl, Uid=> 1, ) or die "IMAP Failure: $@"; $imap->Peek(1);# do NOT mark as read when handling messages $imap->select( $imap_folder ) or die "IMAP Select Error for imap folde +r [$imap_folder]: $@"; # check the connection if ( $imap->IsConnected() ){ print "FOLDERS\n\t",join( "\n\t", $imap->folders ), "\n" if $debug +; voice("$user succesfully authenticated on $server port $port. ". "Checking $imap_folder for incoming messages every $sleep seco +nds."); } else{ print $imap->LastError(); die; } # useful to print something in cmd.exe defaulting on codepage 850 print "changing codepage..\n"; system (1, 'chcp 1252'); print "\n\nCTRL-C to exit the program permitting the IMAP logout\n"; print "CTRL-BREAK to review the current status of the program\n"; # Handle Ctrl-C $SIG{INT} = sub{ print "\n\nLogging out..\n"; voice("IMAP logout..\n"); $imap->logout(); print "Exiting..\n"; exit; }; # Handle Ctrl-Break $SIG{BREAK} = sub{ print '=' x 70, "\n"; print "$0 PID $$\n"; print scalar localtime(time)," - $server ",($imap->IsConnected() ? + 'connected' : 'NOT connected'),"\n"; print "checked messages: ", scalar keys %seen,"\n"; print '=' x 70, "\n"; }; ################################################################### # checking for new messages infinite loop ################################################################### my $now = time; %seen = map { $_ => 1} $imap->sentsince($now); while (1){ my $attempts = 1; my $max_attempts = 12; while ( !$imap->noop ){ print "Reconection retry $attempts..\n"; $imap->reconnect; if( $imap->IsConnected() ){ print "Reconnected succesfully!\n"; last; } else{ sleep 10; die "Impossible to reconnect: $@" if $attempts == $max_att +empts; $attempts++; } } my @msgs = $imap->sentsince($now); foreach my $msg (@msgs){ next if $seen{$msg}; $seen{ $msg }++; process_message( $msg ); } sleep 1 for 1..$sleep; $now = time; } ################################################################### # handling messages ################################################################### sub process_message{ my $msg = shift; # extract some mail details $imap->get_header( $msg, "From" ) =~ /^(.*)\s<(.*)>$/; my $from_name = $1; my $from_addr = $2; my $subj = decode('MIME-Header', $imap->get_header( $msg, "Subjec +t" )); # some decoration print '=' x 70, "\n"; print scalar localtime(time),"\n"; print '=' x 70, "\n"; ##################################### # RULES ##################################### # Flag the message and print subj and part of the body if( $from_addr eq 'larry.wall@example.com' or $subj =~ /^from larry/i ){ # say something fun voice("Larry wrote you!"); # set the Flagged (=important, yellow star in thunderbird) + $imap->set_flag("Flagged",$msg); print "SUBJECT: $subj\nBODY:\n". # cut the body as per --substring paramenter substr(decode('MIME-Header', $imap->body_string( $msg +) ),0, $substr),"\n"; } # mark as read and move it SOMEWHERE # NOTE: this moves the message into INBOX/SOMEWHERE and if the fol +der does not exists # it creates it for you (you might need to 'subscribe' it in order + to see the message) elsif( $from_addr =~ /perl.org$/ ){ voice("New mail from perl.org"); # mark as read $imap->set_flag("Seen",$msg); # move it my $newmsgID = $imap->move( 'INBOX/SOMEWHERE', $msg )or die "C +ould not move: $@\n"; # expunge make effective the move $imap->expunge; # $newmsgID must be valid print "DEBUG: message moved\n" if $debug and $newmsgID; } # DEFAULT RULE else{ voice( "Default rule. New message from: $from_name. Subject: $ +subj" ); } # With the IMAP protocol you can manage five flags : # # \Seen: Message has been read # \Answered: Message has been answered # \Flagged: Message is marked as “flagged” for urgent/special atte +ntion # \Deleted: Message is marked as “to be deleted”. # Note that the actual removal takes effect when the expunge() +function is called, # when you switch the mailbox, or close the connection. # This flag is already added by the delete() function # \Draft: Message has not completed composition (marked as a draft +). # # use them as in (leading slash is optional) # $imap->set_flag("Seen",$msg); # # BE AWARE: if you omit $msg like in $imap->set_flag("Seen") it wi +ll be interpreted as $imap->set_flag("Seen", 'ALL') # THE ABOVE IS TRUE ALSO FOR THESE METHODS: # # bodypart_string # message_uid # body_string # get_bodystructure # get_envelope # fetch_hash (and so also the migrate methods) # flags # parse_headers # size # # https://github.com/plobbes/mail-imapclient/issues/4 print '=' x 70, "\n"; } sub voice{ my $txt = join ' ',@_; $voice->Speak($txt); print "VOICE: $txt\n"; }
L*

There are no rules, there are no thumbs..
Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.

Replies are listed 'Best First'.
Re: Read and manage IMAP mails for me
by cavac (Prior) on Mar 14, 2023 at 15:25 UTC

    Getting the computer to read out (parts of) emails sounds terrifying and hilarious at the same time. Bonus points if you can get it to sound like HAL9000.

    A new business message marked "important" has arrived from Nigeria. Me +ssage as follows: I am Dr. Bakare Tunde, the cousin of Nigerian Astronaut, Air Force Maj +or Abacha Tunde. He was the first African in space when he made a secret flight to the Salyut 6 space station in 1979. He was on a lat +er Soviet spaceflight, Soyuz T-16Z to the secret Soviet military space station Salyut 8T in 1989. He was stranded there in 1990 when th +e Soviet Union was dissolved. His other Soviet crew members returned to earth on the Soyuz T-16Z, but his place was taken up by re +turn cargo. There have been occasional Progrez supply flights to keep him going since that time. He is in good humor, but wants to c +ome home. In the 14-years since he has been on the station, he has accumulated f +light pay and interest amounting to almost $ 15,000,000 American Dollars. This is held in a trust at the Lagos National Savings and Tru +st Association. ...

    PerlMonks XP is useless? Not anymore: XPD - Do more with your PerlMonks XP
Re: Read and manage IMAP mails for me
by Bod (Parson) on Mar 14, 2023 at 23:53 UTC

    It's a pity I've not been able to get Perl to run on my Android mobile even with Termux.

    Driving a good time to listen to emails being read out as one's body is occupied but the mind is mostly free. I've written an Android app to read emails to me whilst driving but it is not very good and needs work before it is properly usable. The problem is that it's in Java...if only I could get Perl on Android...

    Thanks Discipulus for re-igniting that idea...I shall give your code a try...

        Thanks for that LanX

        I haven't looked at Termux for a while although it's been installed for an age. I shall look again soon and see if I can use it to run a useful application...

Re: Read and manage IMAP mails for me
by adamsj (Hermit) on Jul 09, 2025 at 14:38 UTC
    I'm having difficulty getting this code of Discipulus' to work--actually, I think his code is fine and wonder about my dovecot instance--and have stripped things down to a test case:
    #!/usr/bin/perl use strict; use warnings; use Mail::IMAPClient; my $now = time; print "Now is $now\n"; (my $username, my $password) = ('re','dacted'); my $imap = Mail::IMAPClient->new(Server => 'localhost', User => $username, Password => $password, Port => '143'); die "failed to instantiate $@." unless defined $imap; my $select = $imap->select('INBOX'); my @messages = $imap->messages('INBOX'); print "All @messages\n"; sleep 15; #<---Here's where I generate fresh mail, during the sleep $select = $imap->select('INBOX'); @messages = $imap->sentsince($now); print "Some @messages\n"; print "Now then was $now\n"; $now = time; print "Now now is $now\n";
    Which gives me these results:
    Now is 1752071233 All 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3 +456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 Some Now then was 1752071233 Now now is 1752071248
    What I expect is for the new mail to show up in the Some line, but it does not.
    They laughed at Joan of Arc, but she went right ahead and built it. --Gracie Allen
      I haven't had time to look at the details, but I did check the code I use for work that fetches data from mailboxes and we're using Net::IMAP::Simple. You might try that.
Re: Read and manage IMAP mails for me
by adamsj (Hermit) on Jul 03, 2025 at 15:46 UTC
    I'm having some trouble getting this code to recognize incoming mail. It skips right over my @msgs = $imap->sentsince($now); and goes around again. It never detects the incoming mail. The changes I've made are very slight; there's a diff of them in my scratchpad. I'm kind of at a loss. Any thoughts are welcome!
    They laughed at Joan of Arc, but she went right ahead and built it. --Gracie Allen
      Hm, IMAP provides a stable view of a mailbox (so that you can delete or move things by numeric index) until the next time you select a folder. In order to see changes to an inbox, you typically need to re-select it. I'm suspicious that the code above doesn't show signs of doing that.

      I believe there's also a way to get push-notifications from the server, but I don't know the technical details there, just that Thunderbird+Dovecot make that work.

        Good thought! I tried putting $imap->select( $imap_folder ) or die "IMAP Select Error for imap folder [$imap_folder]: $@"; inside the while loop, and still got no joy.
        They laughed at Joan of Arc, but she went right ahead and built it. --Gracie Allen