If you are running Postfix with quota support, email reporting for users that are nearing their quota limit would be highly beneficial in your environment. If aren’t running Postfix with quota support and would like to, see our articles Postfix VDA 2.11.3 Available for Debian 8 or Postfix VDA 2.11.2 Available for Debian 7. Below we are providing a Perl script that will notify mail administrators of current quota usages and email users if they reach a preset percent of quota used. This script is loosely based on this script at How to Forge. In order to run the script below, you will need to place it with your Postfix configuration at /etc/postfix/quota-notify
and make it executable. Once that’s complete, simply edit the variables on lines 4-11 and add an entry to your crontab to run the script as seen below.
#!/usr/bin/perl -w use strict; my $POSTFIX_CF = "/etc/postfix/main.cf"; my $MAILPROG = "/usr/sbin/sendmail -t"; my $WARNPERCENT = 85; my @POSTMASTERS = ('tech@oitibs.com'); my $COADDR = 'OITIBS Automation <tech@oitibs.com>'; my $SUADDR = 'OITIBS Automation <tech@oitibs.com>'; my $MAIL_REPORT = 1; my $MAIL_WARNING = 1; # get virtual mailbox base from postfix config open(PCF, "< $POSTFIX_CF") or die $!; my $mboxBase; while (<PCF>) { next unless /virtual_mailbox_base\s*=\s*(.*)\s*/; $mboxBase = $1; } close(PCF); # assume one level of subdirectories for domain names my @domains; opendir(DIR, $mboxBase) or die $!; while (defined(my $name = readdir(DIR))) { next if $name =~ /^\.\.?$/; #skip '.' and '..' next unless (-d "$mboxBase/$name"); push(@domains, $name); } closedir(DIR); # iterate through domains for username/maildirsize files my @users; chdir($mboxBase); foreach my $domain (@domains) { opendir(DIR, $domain) or die $!; while (defined(my $name = readdir(DIR))) { next if $name =~ /^\.\.?$/; #skip '.' and '..' next unless (-d "$domain/$name"); push(@users, {"$name\@$domain" => "$mboxBase/$domain/$name"}); } } closedir(DIR); # get user quotas and percent used my (%lusers, %uquota, $report); foreach my $href (@users) { foreach my $user (keys %$href) { my $quotafile = "$href->{$user}/maildirsize"; next unless (-f $quotafile); open(QF, "< $quotafile") or die $!; my ($firstln, $quota, $used); while (<QF>) { my $line = $_; if (! $firstln) { $firstln = 1; die "Error: corrupt quotafile $quotafile" unless ($line =~ /^(\d+)S/); $quota = $1; last if (! $quota); next; } die "Error: corrupt quotafile $quotafile" unless ($line =~ /\s*(-?\d+)/); $used += $1; } close(QF); next if (! $used); my $percent = int($used / $quota * 100); $uquota{$user} = "(" . int($quota / 1048576) . "MB)"; $lusers{$user} = $percent unless not $percent; } } # send a report to the postmasters if ($MAIL_REPORT) { open(MAIL, "| $MAILPROG"); select(MAIL); map {print "To: $_\n"} @POSTMASTERS; print "From: $COADDR\n"; print "Subject: Postfix Daily Quota Report\n"; print "Content-Type: text/html\n\n"; print "<html><style> table tbody { border-collapse: collapse; border: 2px solid #C0C0C0; font-family: calibri; } tr td { border: 2px solid #C0C0C0; padding: 8px; } </style><table border=2><tbody>"; print "<tr bgcolor='#333333'><td colspan='3'><b style='color: #ffffff'>Postfix Daily Quota Report By Mailbox</b></td></tr>"; foreach my $luser ( sort { $lusers{$b} <=> $lusers{$a} } keys %lusers ) { printf("<tr><td> %d%%\n </td><td> %s </td><td> %s </td></tr>", $lusers{$luser}, $luser, $uquota{$luser}); } print "</tbody></table></html>"; close(MAIL); } # email a warning to people over quota if ($MAIL_WARNING) { foreach my $luser (keys (%lusers)) { next unless $lusers{$luser} >= $WARNPERCENT; # skip those under quota open(MAIL, "| $MAILPROG"); select(MAIL); print "To: $luser\n"; map {print "BCC: $_\n"} @POSTMASTERS; print "From: $SUADDR\n"; print "Subject: Mailbox Quota Warning: $lusers{$luser}% full.\n"; print "X-Priority: 1 (Highest)\n"; print "X-MSMail-Priority: High\n"; print "Reply-to: $SUADDR\n"; print "Your mailbox: $luser is $lusers{$luser}% full.\n\n"; print "Once your storage quota has been exceeded your mailbox will no longer accept email.\n"; print "Please consider deleting e-mail and emptying your trash folder to free some space.\n\n"; print "If further assistance is required, please reply to this email.\n\n"; print "Open IT Integrated Business Solutions"; close(MAIL); } }
In the end the admin report will look like the image below.