/usr/share/perl5/Mail/Bulkmail.pm is in libmail-bulkmail-perl 3.12-4.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 | package Mail::Bulkmail;
# Copyright and (c) 1999, 2000, 2001, 2002, 2003 James A Thomason III (jim@jimandkoka.com). All rights reserved.
# Mail::Bulkmail is distributed under the terms of the Perl Artistic License.
# Mail::Bulkmail is still my baby and shall be supported forevermore.
=pod
=head1 NAME
Mail::Bulkmail - Platform independent mailing list module
=head1 AUTHOR
Jim Thomason, jim@jimandkoka.com (http://www.jimandkoka.com)
=head1 SYNOPSIS
use Mail::Bulkmail /path/to/conf.file
my $bulk = Mail::Bulkmail->new(
"LIST" => "~/my.list.txt",
"From" => '"Jim Thomason"<jim@jimandkoka.com>',
"Subject" => "This is a test message",
"Message" => "Here is my test message"
) || die Mail::Bulkmail->error();
$bulk->bulkmail() || die $bulk->error;
Don't forget to set up your conf file!
=head1 DESCRIPTION
Mail::Bulkmail gives a fairly complete set of tools for managing mass-mailing lists. I initially
wrote it because the tools I was using at the time were just too damn slow for mailing out to
thousands of recipients. I keep working on it because it's reasonably popular and I enjoy it.
In a nutshell, it allows you to rapidly transmit a message to a mailing list by zipping out the
information to them via an SMTP relay (your own, of course). Subclasses provide the ability to
use mail merges, dynamic messages, and anything else you can think of.
Mail::Bulkmail 3.00 is a major major B<major> upgrade to the previous version (2.05), which
was a major upgrade to the previous version (1.11). My software philosophy is that most code
should be scrapped and re-written every 6-8 months or so. 2.05 was released in October of 2000, and
I'm writing these docs for 3.00 in January of 2003. So I'm at least 3 major re-writes behind.
(philosophy is referenced in the FAQ, below)
But that's okay, because we're getting it done now.
3.00 is about as backwards compatible to 2.00 as 2.00 is to 1.00. That is to say, sorta. I've
tried to make a note of things where they changed, but I'm sure I missed things. Some things can
no longer be done, lots are done differently, some are the same. You will need to change your code
to update from 1.x or 2.x to 3.00, though. That's a given.
So what's new for 3.00? Lots of stuff.
Immediate changes are:
* code compartmentalization
* multi-server support
* conf file
The immediate change is that the code is now compartmentalized.
Mail::Bulkmail now just handles ordinary, non-dynamic mailings. See Mail::Bulkmail::Dynamic for the
merging and dynamic text abilities from the prior versions.
Server connections are no longer handled directly in Mail::Bulkmail (Smtp attribute, Port attribute,
etc.), there is now a separate Mail::Bulkmail::Server object to handle all of that.
And everything subclasses off of Mail::Bulkmail::Object, where I have my super-methods to define
my objects, some helper stuff, and so on.
It's just a lot easier for me to maintain, think about it, etc. if it's all separated. It's also easier
for you, the user, if you want to make changes to things. Just subclass it, tweak it, and use it.
Very straightforward to modify and extend now. 2.x and below *could* do it, but it wasn't really that
easy (unless you were making very trivial changes). This should rectify that.
Another major change is the addition of multi-server support. See the docs in Mail::Bulkmail::Server for
more information. You can still specify one SMTP relay if that's all you've got, but if you have multiple
servers, Mail::Bulkmail can now load balance between them to help take the stress off. No matter what,
the biggest bottleneck to all of this is network performance (both to the SMTP relay and then from
the relay to the rest of the world), so i wanted to try and help alleviate that by using multiple servers.
I know that some people were doing that on there own with small changes, but this allows you to do it all
invisibly.
And finally, finally, finally there is a conf file. Documentation on the format is in Mail::Bulkmail::Object.
It's pretty easy to use. This is the conf file format that I designed for my own use (along with most of the
rest of Mail::Bulkmail::Object). The software also has the ability to read multiple conf files, if so
desired. So no more worrying about asking your sysadmin to tweak the values in your module somewhere up in /usr/lib/whatever
Just have him create the conf file you want, or pass in your own as desired.
conf_files are specified and further documented in Mail::Bulkmail::Object, in an internal array called @conf_files, right
at the top of the module. To specify a universal conf file, put it in that array (or have your sysadmin do so).
Alternatively, you can also add a conf_file via the conf_files accessor.
Mail::Bulkmail->conf_files('/path/to/conf_file', '/path/to/other/conf_file'); #, etc.
But the recommended way is to specify your conf file upon module import.
use Mail::Bulkmail 3.00 "/path/to/conf/file";
In addition, there is the usual plethora of bug fixes, tweaks, clean-ups, and so on.
And yes, the horrid long-standing bug in the Tz method is B<fixed!> No, honest.
I'm also trying a new documentation technique. The pod for a given method is now in the module by that
method, as opposed to everything being bunched up at the bottom. Personally, I prefer everything being bunched
up there for clarities sake. But from a maintenance point of view, spreading it all out makes my life much easier.
=head1 requires
Perl 5.6.0, Socket
(It probaly can get by with less than 5.6.0, but I haven't tested it in such an environment)
=cut
use Mail::Bulkmail::Object;
@ISA = Mail::Bulkmail::Object;
$VERSION = '3.12';
use Socket;
use strict;
use warnings;
=head1 ATTRIBUTES
=over 11
=cut
#attributes for storing important headers
# you'll note that these 5 attributes are email addresses and don't use the standard add_attr
# instead, they're wrapped to call _email_accessor internally instead of _accessor as normal.
# Externally, it's the same. $obj->From($value) sets it and $obj->From() reads it
#
# But this also creates additional internal methods for the slots. So there is a ->From and a ->_From
# for example. ->_From internally stores whatever is accepted by ->From, and same with the rest of them.
# Don't access the ->_ attributes directly, use the wrappers instead.
=pod
=item From
Stores the From address of this mailing. Must be a valid email address, unless Trusting is set.
Really really should be a valid email address anyway.
From is no longer used as the Sender, as was the behavior in prior versions. Now, Mail::Bulkmail
first tries to use the Sender as the Sender, and failing that, falls back on the from.
$bulk->From('"Jim Thomason"<jim@jimandkoka.com>');
print $bulk->From;
=cut
__PACKAGE__->add_attr(["From", '_email_accessor'], 0);
=pod
=item To
Stores the To address of this mailing. Must be a valid email address, unless Trusting is set.
Really should be a valid email address anyway.
To is used if you have use_envelope set to 1. See use_envelope, below. If you are not using the envelope,
then the actual email address that we are currently on is used instead and ->To is never used at all.
$bulk->To('jimslist:;');
print $bulk->To;
As of 3.00, ->To may contain either a valid email address or a valid group definition. A group definition is as follows
(pseudo-regex):
Groupname:(address(,address)*)?;
i.e., "the group name", then a colon, then an optional list of email addresses, then a semi-colon
$bulk->To('jim@jimandkoka.com');
$bulk->To('MyList:jim@jimandkoka.com');
$bulk->To('MyList:;');
Are all valid addresses. Only the ->To attribute may accept group syntax emails
=cut
__PACKAGE__->add_attr(["To", '_email_accessor'], 1);
=pod
=item Sender
Stores the Sender address of this mailing. Must be a valid email address, unless Trusting is set.
Really really should be a valid email address anyway.
Sender is mainly used when speaking SMTP to the server, specifically in the RCPT TO command.
The spec defines "Sender" as "he who send the message" (paraphrasing), which may not actually be who
the message is from. 2.00 used the From address as the Sender.
You should specify this, but if you don't then the From value is assumed to be the sender.
$bulk->Sender('jim@jimandkoka.com');
print $bulk->Sender;
If this value is not set, then Mail::Bulkmail B<will> place a Sender header equal to the From value.
Note that the ultimate receiving SMTP server is expected to place a Return-Path header in the message. This
Return-Path value will be set to the value of the sender of the message, either ->Sender or ->From. This, in
turn, will be the address that bounce backs go to. You should not set a Return-Path header yourself, because bad things
will result.
=cut
__PACKAGE__->add_attr(["Sender", '_email_accessor'], 0);
=pod
=item ReplyTo
Stores the Reply-To address of this mailing. Must be a valid email address, unless Trusting is set.
Really really should be a valid email address anyway.
Reply-To is used as the address that the user's email client should reply to, if present. If this
value is not set, then Mail::Bulkmail B<will> place a Reply-To header equal to the From value.
Note that even though the attribute is "ReplyTo", the header set is "Reply-To"
$bulk->ReplyTo('jim@jimandkoka.com');
print $bulk->ReplyTo;
=cut
__PACKAGE__->add_attr(["ReplyTo", '_email_accessor'], 0);
=pod
=item Subject
Boring old accessor that stores the subject of the message. It's really recommended that this is
set either at your object or in the conf file, otherwise you'll send out a mailing list with no subject
which will probably be ignored.
$bulk->Subject("This is the list you signed up for");
print $bulk->Subject;
=cut
__PACKAGE__->add_attr("Subject");
# internally stores the Precedence of the bulkmail object. Should never be accessed
# directly, should always be accessed via the ->Precedence method, which does a validation check
__PACKAGE__->add_attr("_Precedence");
# internally stores all non-standard (read: "not defined above") headers that the bulkmail object
# may have. It's stored as a hashref, and should be accessed via the ->header method.
__PACKAGE__->add_attr('_headers');
# internally stores the _cached_headers for a given message. This is populated by the
# buildHeaders() method during mailing. After the headers have been built once, then
# buildHeaders returns the value in _cached_headers instead of constantly rebuilding them.
#
# _cached_headers is static if using the envelope. If not using the envelope, then the
# string ##EMAIL## is populated into the To: header, and buildHeaders swaps that for the
# actual individual email addresses
__PACKAGE__->add_attr('_cached_headers');
#attributes for storing boolean flags
=pod
=item HTML
Boolean flag. 1/0 only.
A lot of people, though obviously not you, because you're reading the pod, just couldn't figure out how
to send HTML messages. It's easy.
$bulk->header("Content-type", "text/html");
But it was just too hard for most people. So I added this flag.
Here's the order:
Check and see if ->header("Content-type") is set, if so then send it.
Otherwise, check and see if ->HTML is true, if so, then send a content-type of text/html
i.e., an HTML message
Otherwise, send a content-type of text/plain
i.e., a plaintext message
$bulk->HTML(1);
print $bulk->HTML();
=cut
__PACKAGE__->add_attr('HTML');
=pod
=item use_envelope
Boolean flag. 1/0 only.
use_envelope was the coolest thing I added to Bulkmail 2.00, and is arguably still the best thing I've got
here in terms of raw power in your lists.
Basically, it's like lasing a stick of dynamite. Mail::Bulkmail is fast. Mail::Bulkmail with use_envelope
is mind-numbingly fast.
For the uninformed, an email message contains two parts, the message itself and the envelope. Mail servers only
care about the envelope (for the most part), since that's where they find out who the message is to and from, and
they don't really need to know anything else.
A nifty feature of the envelope is that you can submit multiple addresses within the envelope, and then your
mail server will automagically send along the message to everyone contained within the envelope. You end up
sending a hell of a lot less data across your connection, your SMTP server has less work to do, and everything
ends up working out wonderfully.
There are two catches. First of all, with envelope sending turned off, the recipient will have their own email
address in the "To" field (To: jim@jimandkoka.com, fer instance). With the envelope on, the recipient will only
receive a generic email address ("To: list@myserver.com", fer instance) Most people don't care since that's
how most email lists work, but you should be aware of it.
Secondly, you B<MUST> and I mean B<MUST> sort your list by domain. Envelopes can only be bundled up by domain,
so that we send all email to a domain in one burst, all of the email to another domain in the next burst, and so
on. So you need to have all of your domains clustered together in your list. If you don't, your list will still
go out, but it will be a B<lot> slower, since Mail::Bulkmail has a fair amount more processing to do when you send
with then envelope. This is normally more than offset by the gains received from sending fewer messages. But with
an unsorted list, you never see the big gains and you see a major slow down. Sort your lists.
$bulk->use_envelope(0);
print $bulk->use_envelope;
=cut
__PACKAGE__->add_attr('use_envelope');
=pod
=item force80
Boolean flag 1/0
RFC 2822 recommends that all messages have no more than 80 characters in a line (78 + CRLF), but doesn't require it. if force80 is 1,
then it will force a message to have only 80 characters per line. It will try to insert carriage returns between word boundaries,
but if it can't, then it will cut words in half to force the limit.
Regardless of force80, be warned that RFC 2822 mandates that messages must have no more than 1000 characters per line (998 + CRLF),
and that wrapping will be done no matter what. Again, it will try to wrap at word boundaries, but if it can't, it will cut words
in half to force the limit.
It is recommended that you just have your message with at most 78 characters + CRLF for happiness' sake, and B<definitely> at most
998 characters + CRLF. You may end up with extra CRLFs in your message that you weren't expecting.
If your message is not guaranteed to have only < 78 characters + CRLF per line, then it's recommended to have force80 on for
full compatibility. Note that force80 will be overridden by ->Trusting('wrapping');
=cut
__PACKAGE__->add_attr('force80');
# internal flag to let ->bulkmail know if a message is waiting. This is necessary for envelope sending:
# when we get a new domain from the getNextLine call on LIST, we need to see if there's a waiting message
# first. If there is a waiting message, then we need to finish that one up before we start the next one
# for the new domain. _waiting_message stores that value
__PACKAGE__->add_attr("_waiting_message");
#attributes for storing connection information
=pod
=item servers
arrayref of servers.
Okay, this is the first major change between 2.x and 3.x. 2.x had methods to connect to one server (->Smtp, ->Port, etc.).
3.x doesn't have those, and the relevent things are now in Mail::Bulkmail::Server, instead it has a list of servers.
servers should contain an arrayref of server objects. You can either create them externally yourself and pass them in in an arrayref,
$bulk->servers([\$server, \$server2, \$server3]);
or you can create them in your conf file. See the Mail::Bulkmail::Object for more info on the format of the conf file, and
Mail::Bulkmail::Server for the attributes to specify.
servers will automatically be populated with a list of all servers in the server_list in the conf file if you don't specify anything,
so you really don't need to worry about it.
If you'd rather use a different server_file, then pass the server_file flag to the constructor:
$bulk = Mail::Bulkmail->new(
'server_file' => '/path/to/server_file'
);
That will B<override and ignore> the server_file in B<any> conf file, so use it with caution.
Realistically, though, just let the program populate in the values of the servers you specified in the conf file and don't worry about
this.
Be warned that servers will be populated by the constructor if you do not populate servers at object creation. You may still
change servers later (before you begin mailing), but there is the slight performance hit to initialize all of the server objects
and then throw them away. This doesn't affect mailing speed in anyway, it'll just take a little longer to get started than it should.
=cut
__PACKAGE__->add_attr('servers');
# internal flag to let ->bulkmail know the domain of the last email address we looked at when using
# the envelope. This is necessary to know when we reach a new domain in the LIST. If we have a new
# domain (i.e., the current message's domain is different from _cached_domain), then finish off the
# message if we _waiting_message is true and then move on
__PACKAGE__->add_attr("_cached_domain");
# internally stores which index of the ->servers list we're on used and set by nextServer
__PACKAGE__->add_attr("_server_index");
#attributes for storing information about the message
=pod
=item Message
This stores the message that you will send out to the recipients of your list.
$bulk->Message('Hi there. You're on my mailing list');
print $bulk->Message;
Don't put any headers in your Message, since they won't be transmitted as headers. Instead they will show up in the body
of your message text. Use the ->header method instead for additional headers
This mutator is known to be able to return:
MB020 - could not open file for message
MB021 - could not close file for message
MB022 - invalid headers from message
=cut
# The message is actually stored internally (_Message) and accessed via Message.
# That way, if we change the message, we can be sure to wipe out the internal _cached_message as well
__PACKAGE__->add_attr('_Message');
sub Message {
my $self = shift;
$self->_cached_message(undef) if @_;
my @passed = @_;
my $needs_header_extraction = 0;
if (@passed) {
$self->_extracted_headers_from_message(0);
};
if ($self->message_from_file) {
my $file = shift @passed || $self->_message_file;
if (! defined $self->_message_file_access_time || $file ne $self->_message_file || -M $file < $self->_message_file_access_time) {
$self->_message_file($file);
$self->_message_file_access_time(-M $file);
#theoretically, you could call ->Message with no arguments but with message_from_file turned on
#in that case, you may re-read the file if it's been modified since you last looked at it.
#We're currently in that case. So we wipe out the previously _cached_message to be safe.
$self->_cached_message(undef);
my $handle = $self->gen_handle;
my $message = undef;
open ($handle, $file) || return $self->error("Could not open file for message: $!", "MB020");
{
local $/ = undef;
$message = <$handle>;
}
close ($handle) || return $self->error("Could not close file for message: $!", "MB021");
unshift @passed, $message;
};
};
#first, wipe out any previously set headers_from_message
if (defined $self->_previous_headers_from_message) {
foreach my $header (@{$self->_previous_headers_from_message}){
$self->header($header, undef);
};
};
#wipe out the list of previously set headers
$self->_previous_headers_from_message([]);
#then, if we're setting new headers, we should set them.
if ($self->headers_from_message && ! $self->_extracted_headers_from_message) {
$self->_extracted_headers_from_message(1);
$passed[0] ||= $self->_Message(); #We'll sometimes call this method after setting the message
#sendmail-ify our messages newlines
$passed[0] =~ s/(?:\r?\n|\r\n?)/\015\012/g;
my $header_string = undef;
#split out the header string and the message body
($header_string, $passed[0]) = split(/\015\012\015\012/, $passed[0], 2);
my ($last_header, $last_value) = ();
foreach (split/\015\012/, $header_string){
if (/:/){
if (defined $last_header && defined $last_value) {
#set our header
$self->header($last_header, $last_value)
|| return undef; #bubble up the header error
#and wipe out the prior values
$last_header = $last_value = undef;
};
($last_header, $last_value) = split(/:/, $_, 2);
push @{$self->_previous_headers_from_message}, $last_header;
}
elsif (/^\s+/){
$last_value .= "\015\012$_";
}
else {
return $self->error("Invalid Headers from Message: line ($_)\n\n-->($header_string)", "MB022");
};
};
#clean up any headers that remain
if (defined $last_header && defined $last_value) {
#set our header
$self->header($last_header, $last_value)
|| return undef; #bubble up the header error
};
};
return $self->_Message(@passed);
};
# internal method. Looks to see if a the message is being read from disk. If so, if it
# was modified since it was read, then it is not current. Otherwise, it is.
sub _current_message {
my $self = shift;
if (
$self->message_from_file
&& (
! defined $self->_message_file_access_time
|| -M $self->_message_file < $self->_message_file_access_time
)
) {
return 0;
}
else {
return 1;
};
};
# internally stores the _cached_message for a given message. This is populated by the buildMessage()
# method during mailing. After the message has been built once, then buildMessage returns the
# value in _cached_message instead of constantly rebuilding it.
__PACKAGE__->add_attr('_cached_message');
=pod
=item message_from_file
boolean flag. 1/0 only.
message_from_file allows you to load your message in from a file. If message_from_file is
set to 1, then the value passed to ->Message() will be assumed to be a path to a file on disk.
That file will be openned in read mode (if possible), read in, and stored as your message. Note
that your entire message text will be read into memory - no matter how large the message may be.
This is simply a shortcut so that you don't have to open and read in the message yourself.
B<NOTE> This is a bit picky, to put it mildly. No doubt you've read that the constructor actually
is taking in its arguments in an array, not a hash. So they're parsed in order, which means you need
pass in message_from_file B<before> Message. i.e., this will work:
$bulk = Mail::Bulkmail->new(
'message_from_file' => 1,
'Message' => '/path/to/message.txt',
);
But this will not:
$bulk = Mail::Bulkmail->new(
'Message' => '/path/to/message.txt',
'message_from_file' => 1,
);
Ditto for using the mutators. Turn on the flag, i<then> specify the Message.
=cut
__PACKAGE__->add_attr('message_from_file');
# internal caching attribute to store the message file. This way we will be able to re-open
# and re-read the message file if it happened to change.
__PACKAGE__->add_attr('_message_file');
# internal attribute to store the time the message file was last accessed. This allows the message
# file to change and be re-read, though lord knows why you'd want to necessarily do something like
# that.
__PACKAGE__->add_attr('_message_file_access_time');
=pod
=item headers_from_message
boolean flag. 1/0 only.
headers_from_message allows you to specify mail headers inside your message body. You may
still specify additional headers in the traditional manner.
Note that if you change the value of ->Message (not recommended, but there are times you may
want to do so), then any headers that were previously set via headers_from_message will be B<wiped out>.
any headers specified in the message will be set when you call ->Message.
=cut
__PACKAGE__->add_attr('headers_from_message');
# internal boolean flag. used to govern whether the headers have already been extracted from
# the message
__PACKAGE__->add_attr('_extracted_headers_from_message');
#internal arrayref containing the headers set the last time ->Message was called.
__PACKAGE__->add_attr("_previous_headers_from_message");
# internal hashref that stores the list of duplicate email addresses populated by setDuplicate and
# read by isDuplicate. WARNING - there is a *severe* penalty for using duplicates, this hash can
# get really really huge. It is recommended you remove duplicates in advance and turn on
# allow_duplicates to prevent this from being populated, if you do use it, then it
# is *strongly* recommended that you leave Trusting('banned') off, i.e. Trusting('banned' => 0)
__PACKAGE__->add_attr('_duplicates');
# internal hashref that stores the list of banned email addresses or domains populated by a call
# to banned (which does some magic with _file_accessor). accessed via isBanned
# It is *strongly* recommended that you leave Trusting('banned') off, i.e. Trusting('banned' => 0)
__PACKAGE__->add_attr('_banned');
#attributes for storing filehandles
=pod
=item LIST
LIST stores the list of addresses you're going to mail out to. LIST may be either a coderef, globref, arrayref, or string literal.
If a string literal, then Mail::Bulkmail will attempt to open that file as your list:
$bulk->LIST("/path/to/my/list");
If a globref, it is assumed to be an open filehandle:
open (L, "/path/to/my/list");
$bulk->LIST(\*L);
if a coderef, it is assumed to be a function to return your list, or undef when it is done:
sub L {return $listquery->execute()}; #or whatever your code is
$bulk->LIST(\&L);
The coderef will receive the bulkmail object itself as an argument.
if an arrayref, it is assumed to be an array containing your list:
my $list = [qw(jim@jimandkoka.com thomasoniii@yahoo.com)];
$bulk->LIST($list);
Use whichever item is most convenient, and Mail::Bulkmail will take it from there.
=cut
__PACKAGE__->add_attr(['LIST', '_file_accessor'], '<');
=pod
=item BAD
This is an optional log file to keep track of the bad addresses you have, i.e. banned, invalid, or duplicates.
BAD may be either a coderef, globref, arrayref, or string literal.
If a string literal, then Mail::Bulkmail will attempt to open that file (in append mode) as your log:
$bulk->BAD("/path/to/my/bad.addresses");
If a globref, it is assumed to be an open filehandle in append mode:
open (B, ">>/path/to/my/bad.addresses");
$bulk->BAD(\*L);
if a coderef, it is assumed to be a function to call with the address as an argument:
sub B { print "BAD ADDRESS : ", $_[1], "\n"}; #or whatever your code is
$bulk->BAD(\&B);
The coderef will receive two arguments. The first is the bulkmail object itself, and the second
is the data in the form that it was returned from the LIST attribute.
if an arrayref, then bad addresses will be pushed on to the end of it
$bulk->BAD(\@bad);
Use whichever item is most convenient, and Mail::Bulkmail will take it from there.
=cut
__PACKAGE__->add_attr(['BAD', '_file_accessor'], '>>');
=pod
=item GOOD
This is an optional log file to keep track of the good addresses you have, i.e. the ones that
Mail::Bulkmail could successfully transmit to the server. Note that there is no guarantee that
an email address in the GOOD file actually received your mailing - it could have failed at a
later point when out of Mail::Bulkmail's control.
GOOD may be either a coderef, globref, arrayref, or string literal.
If a string literal, then Mail::Bulkmail will attempt to open that file (in append mode) as your log:
$bulk->GOOD("/path/to/my/good.addresses");
If a globref, it is assumed to be an open filehandle in append mode:
open (B, ">>/path/to/my/good.addresses");
$bulk->GOOD(\*B);
if a coderef, it is assumed to be a function to call with the address as an argument:
sub G { print "GOOD ADDRESS : ", $_[1], "\n"}; #or whatever your code is
$bulk->GOOD(\&G);
The coderef will receive two arguments. The first is the bulkmail object itself, and the second
is the data in the form that it was returned from the LIST attribute.
if an arrayref, then bad addresses will be pushed on to the end of it
$bulk->GOOD(\@good);
Use whichever item is most convenient, and Mail::Bulkmail will take it from there.
Please note that ->GOOD only says that the address was initially accepted for delivery. It could later fail while transmitting
the email address, or it could be an valid but non-existent address that bounces later. It is up to the end user to inspect your
error logs to make sure no errors occurred, and look for (and weed out) bounces or other failures later.
=cut
__PACKAGE__->add_attr(['GOOD', '_file_accessor'], '>>');
#class attributes
=pod
=item server_class
server_class is a class method that B<MUST> be specified in the conf file. You can initialize it in your program if you
really want, but it is B<strongly> recommended to be in the conf file so you don't forget it.
server_class is used by the constructor to create the server list to populate into ->servers, ->servers is not
populated in the constructor.
By default, this should probably be Mail::Bulkmail::Server, to allow mailing. Another useful value is Mail::Bulkmail::Dummy
See Mail::Bulkmail::Server and Mail::Bulkmail::Dummy for more information on how to create those objects.
Also, if you write your own server implementation, this would be where you'd hook it into Mail::Bulkmail
=cut
__PACKAGE__->add_class_attr('server_class');
#speciality accessors
# _Trusting stores the hashref that is accessed internally by the Trusting method
__PACKAGE__->add_attr('_Trusting');
=pod
=item Trusting
Trusting specifies your Trusting level. Mail::Bulkmail 3.00 will do its best to make sure that your email addresses
are valid and that your message conforms to RFC 2822. But, there is a slight performance hit to doing that - it does have
to check things, do regexes, and so on. It's not very slow, but extrapolated over a huge list, it can be noticeable.
So that's where Trusting comes in to play. If you set a Trusting value, then certain tests will be skipped. B<Use this at your
own risk>. If you tell Mail::Bulkmail to be Trusting, then it won't verify addresses or to make sure your list is under 1,000
characters per line. So if you're Trusting and you pass in bad data, it's your funeral. If there is B<any> chance of invalid data,
then don't be Trusting. If you're *positive* there's nothing wrong, then you may be Trusting.
Trusting values are set one as key/value pairs.
$bulk->Trusting("email" => 1);
$bulk->Trusting("wrapping" => 1);
$bulk->Trusting("default" => 1);
And read back with just the key:
$bulk->Trusting("email");
$bulk->Trusting("wrapping");
$bulk->Trusting("default");
default is used as a fall back. So if you didn't specify a Trusting value for "email", for example, it will use
the "default" value. Note that the default is only used if a value is not specified.
$bulk->Trusting("default" => 1);
print $bulk->Trusting("email"); #prints 1
print $bulk->Trusting("default"); #prints 1
$bulk->Trusting("default" => 0);
print $bulk->Trusting("email"); #prints 0
print $bulk->Trusting("default"); #prints 0
$bulk->Trusting("email" => 1);
print $bulk->Trusting("email"); #prints 1
print $bulk->Trusting("default"); #prints 0
$bulk->Trusting("email" => 0);
$bulk->Trusting("default" => 0);
print $bulk->Trusting("email"); #prints 0
print $bulk->Trusting("default"); #prints 1
You may also directly set all values with the integer short cut.
$bulk->Trusting(1); # everything is Trusting
$bulk->Trusting(0); # nothing is Trusting
If you want to specify Trusting in the conf file, you may only directly specify via the integer shortcut. Otherwise, you must
use the list equation.
# all Trusting
Trusting = 1
#none Trusting
Trusting = 0
#email is trusting
Trusting @= email
Trusting @= wrapping
This will not work:
Trusting = email
If you use that syntax, it will internally do:
$bulk->Trusting('email');
which you know will only read the value, not set it. If you use the array syntax, it will properly set the value.
Note that ->Trusting('default' => 0) is not equivalent to ->Trusting(0). Consider:
$bulk->Trusting('email' => 1);
print $bulk->Trusting('email'); # prints 1
$bulk->Trusting("default' => 0);
print $bulk->Trusting('email'); # still prints 1
$bulk->Trusting(0);
print $bulk->Trusting('email'); # now prints 0
Currently, you may set:
email - Trusting('email' => 1) will not check for valid email addresses
wrapping - Trusting('wrapping' => 1) will not try to wrap the message to reach the 1,000 character per line limit
duplicates - Trusting('duplicates' => 1) will not do any duplicates checking
(this is the equivalent of allow_duplicates in older versions)
banned - Trusting('banned' => 1) will not lowercase the local part of a domain in a banned or duplicates check
(this is the opposite of safe_banned in older versions. i.e. $bulk2_05->safe_banned(1) == $bulk_300->Trusting('banned' => 0);
It is recommended your conf file be:
Trusting @= duplicates
Since you're usually better off weeding duplicates out in advance. All other Trusting values are recommended to be false.
=cut
sub Trusting {
my $self = shift;
my $key = shift;
$self->_Trusting({}) unless $self->_Trusting;
if (defined $key) {
if (ref $key eq "ARRAY"){
foreach my $k (@$key){
$self->_Trusting->{$k} = 1;
};
return 1;
}
elsif (@_){
my $val = shift;
$self->_Trusting->{$key} = $val;
return $val;
}
elsif ($key =~ /^[10]$/){
$self->_Trusting({});
$self->_Trusting->{'default'} = $key;
return $key;
}
else {
return defined $self->_Trusting->{$key}
? $self->_Trusting->{$key}
: ($self->_Trusting->{'default'} || 0)
};
}
else {
return $self->_Trusting->{'default'} || 0;
};
};
=pod
=item banned
banned stores the list of email addresses and domains that are banned. Only store user@domain.com portions of
email addresses, don't try to ban "Jim"<jim@jimandkoka.com>, for instance. Only ban jim@jimandkoka.com
banned may be either a coderef, globref, arrayref, or string literal.
If a string literal, then Mail::Bulkmail will attempt to open that file (in append mode) as your log:
$bulk->banned("/path/to/my/banned.addresses");
If a globref, it is assumed to be an open filehandle in append mode:
open (B, ">>/path/to/my/banned.addresses");
$bulk->banned(\*B);
files should contain one entry per line, each entry being an email address or a domain. For example:
jim@jimandkoka.com
jimandkoka.com
foo@bar.com
bar.com
if a coderef, it is assumed to be a function to return your banned list:
sub B {return $bannedquery->execute()}; #or whatever your code is
$bulk->banned(\&B);
The function should return one entry per execution, either an address or a domain.
if an arrayref, then it's an array of banned addresses and domains
$bulk->banned([qw(jim@jimandkoka.com jimandkoka.com)]);
The arrayref can contain email addresses and domains.
Use whichever item is most convenient, and Mail::Bulkmail will take it from there.
Once banned has been populated, the values are stored internally in a hashref.
=cut
sub banned {
my $self = shift;
if (@_) {
my $banned = shift;
#we're gonna cheat and populate the data into ->_banned via the _file_accessor.
#then we'll iterate through it all, pop it into a hash, and then drop
#that back into _banned instead
my $ob = $self->_banned(); #save it for below.
$self->_file_accessor("_banned", "<", $banned);
my $b = $ob || {}; #keep the old value, or make a new hashref
while (my $address = $self->getNextLine($self->_banned)){
$b->{$address} = 1;
};
return $self->_banned($b);
}
else {
#if we have a banned hash, return it.
if ($self->_banned){
return $self->_banned;
}
#otherwise, create one and return that.
else {
return $self->_banned({});
};
};
};
=pod
=item Precedence
Precedence is a validating accessor to validate the Precedence you have passed for your mailing list.
Precedence must be either:
* list (default) - a mailing list
* bulk - bulk mailing of some type
* junk - worthless test message.
You can use an alternate Precedence if you set Trusting to 0. But seriously, there's *no* reason to do that. Keeping
the appropriate precedence will help the servers on the internet route your message as well as the rest of the email out
there more efficiently. So don't be a jerk, and leave it as one of those three.
This method is known to be able to return:
MB001 - invalid precedence
=cut
sub Precedence {
my $self = shift;
my $prop = '_Precedence';
if (@_){
my $precedence = shift;
if ($self->Trusting('precedence') || $self->_valid_precedence($precedence)){
$self->_Precedence($precedence);
return $self->_Precedence;
}
else {
return $self->error("Invalid precedence: $precedence", "MB001");
};
}
else {
return $self->_Precedence || 'list'; #if they didn't set it, assume list, no matter what
};
};
#date and tz are actually methods, not accessors, but they're close enough, so what the hell
=pod
=item Tz
Returns the timezone that you're in. You cannot set this value. You'll also never need to worry about it.
=cut
sub Tz {
my $self = shift;
my $time = shift || time;
my ($min, $hour, $isdst) = (localtime($time))[1,2,-1];
my ($gmin, $ghour, $gsdst) = (gmtime($time))[1,2, -1];
my $diffhour = $hour - $ghour;
$diffhour = $diffhour - 24 if $diffhour > 12;
$diffhour = $diffhour + 24 if $diffhour < -12;
($diffhour = sprintf("%03d", $diffhour)) =~ s/^0/\+/;
return $diffhour . sprintf("%02d", $min - $gmin);
};
=pod
=item Date
Returns the date that this email is being sent, in valid RFC format. Note that this will be stored in _cached_headers as the
date that the first email is sent.
Another thing you won't need to worry about.
=cut
sub Date {
my $self = shift;
my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
my @days = qw(Sun Mon Tue Wed Thu Fri Sat);
my $time = time;
my ($sec, $min, $hour, $mday, $mon, $year, $wday) = localtime($time);
return sprintf("%s, %02d %s %04d %02d:%02d:%02d %05s",
$days[$wday], $mday, $months[$mon], $year + 1900, $hour, $min, $sec, $self->Tz($time));
};
#done with speciality accessors
#our generic speciality accessors
# internally used to populate the attributes that are expected to contain email addresses
# basically, it just does a valid_email check on the email address before allowing it into
# the object's attribute. The validation check will be bypassed if Trusting is set
#
# otherwise, the attribute externally behaves just as any other
sub _email_accessor {
my $self = shift;
my $prop = shift;
my $allow_groups = shift;
if (@_){
my $email = shift;
if (! defined $email || $self->Trusting('email') || $self->valid_email($email, $allow_groups)){
my $return = $self->$prop($email);;
return defined $email ? $return : 0;
}
else {
return $self->error("Invalid address: $email", "MB002");
};
}
else {
return $self->$prop();
};
};
#done with generic specialty accessors
#constructor
=pod
=back
=head1 METHODS
=over 11
=item new
The constructor, used to create new Mail::Bulkmail objects. See Mail::Bulkmail::Object for more information on constructors.
In a nutshell, the constructor accepts a hash with name/value pairs corresponding to attributes and attribute values.
So that:
my $bulk = Mail::Bulkmail->new(
'LIST' => './list.txt',
'Message' => "This is my message!",
'HTML' => 0
) || die Mail::Bulkmail->error;
is the same as:
my $bulk = Mail::Bulkmail->new() || die Mail::Bulkmail->error;
$bulk->LIST("./list.txt");
$bulk->Message("This is my message!");
$bulk->HTML(0);
*technically* it's not exactly the same, since the constructor will fail with an error if your attribute calls return undef, but
it's close enough.
It is recommend to tack on an || die after your new() calls, to make sure you're alerted if your object isn't created.
my $bulk = Mail::Bulkmail->new() || die Mail::Bulkmail->error();
Otherwise, you won't be alerted if your object isn't created.
Upon creation, Mail::Bulkmail will first iterate through the conf file and populate all of the attributes defined in the conf file
into your object. It will then iterate through the values you passed to the constructor and mutate the attributes to those
values. If you don't pass any arguments to the constructor, it still gets the default values in the conf file. Values passed to
the constructor always override values specified in the conf file
There is one special constructor flag, "server_file", which does not correspond to an attribute or method. "server_file" is used to
override the server_file specified in the conf file.
If you pass a key/value pair to the constructor that doesn't have a corresponding attribute, then it is assuming you are setting a
new header.
my $bulk = Mail::Bulkmail->new('foo' => 'bar');
is the same as:
my $bulk = Mail::Bulkmail->new();
$bulk->header('foo' => 'bar');
This method is known to be able to return:
MB003 - could not use server class
=cut
sub new {
my $class = shift;
my %init = @_;
my $self = $class->SUPER::new(
'servers' => [],
'_headers' => {},
"_duplicates" => {},
"_waiting_message" => 0,
"_server_index" => -1,
@_
) || return undef;
#now, we iterate through everything else that was passed, since we're gonna assume
#that they want to set it as a header
foreach my $key (grep {! $self->can($_)} keys %init){
next if $key eq 'server_file'; #special case to allow passing of a separate server_file
$self->header($key, $init{$key}) || return $class->error($self->error, $self->errcode, 'not logged');
};
#if we have no servers, but we do have a server file (which we should...)
if ($class->server_class) {
$@ = undef;
eval "use " . $class->server_class;
return $self->error("Could not use " . $class->server_class . " : $@", "MB003") if $@;
#if we have no servers, then initialize them via create_all_servers
$self->servers($class->server_class->create_all_servers($init{'server_file'} || undef))
if $class->server_class && @{$self->servers} == 0;
};
return $self;
};
=pod
=item header
the header method is used to set additional headers for your object that don't have their own methods (such as Subject)
header expects the header and value to act as a mutator, or the header to act as an accessor.
$bulk->header('X-Header', "My header value");
print $bulk->header('X-Header'); #prints "My header value"
Use this to set any additional headers that you would like.
Note that you can't use this to bypass validation checks.
$bulk->Header("Subject", "My Subject") will internally change into $bulk->Subject("My Subject");
There's no benefit to doing that, it'll just slow you down.
If you call header with no values, it returns the _headers hashref, containing key value pairs of header => value
This method is known to be able to return:
MB004 - cannot set CC or BCC header
MB005 - invalid header
=cut
#header allows us to specify additional headers
sub header {
my $self = shift;
my $header = shift || return $self->_headers;
if ($header =~ /^(?:From|To|Sender|Reply-?To|Subject|Precedence)$/){
$header =~ s/\W//g;
return $self->$header(@_);
}
elsif ($header =~ /^b?cc/i){
return $self->error("Cannot set CC or BCC...that's just common sense!", "MB004");
}
else {
if ($header =~ /^[\x21-\x39\x3B-\x7E]+$/){
my $value = shift;
if (defined $value) {
$self->_headers->{$header} = $value;
return $value;
}
else {
delete $self->_headers->{$header};
return 0; #non-true value (didn't set it to anything), but a defined value since it's not an error.
};
}
else {
return $self->error("Cannot set header '$header' : invalid. Headers cannot contain non-printables, spaces, or colons", "MB005");
};
};
};
#validation methods
{
# Mail::Bulkmail 3.00 has a greatly extended routine for validating email addresses. The one in 2.x was pretty good,
# but was only slightly superior to the one in 1.x. It also wasn't quite perfect - there were valid addresses it would
# refuse, and invalid addresses it would accept. It was *mostly* fine, though.
#
# 3.00 has a higher standard, though. :)
# So valid_email has been re-written. This should match only valid RFC 2822 addresses, with deviations from the
# spec noted below. Still only allows single addresses, though. No address lists or groups for the general case.
# our regexes to deal with whitespace and folding whitespace
my $wsp = q<[ \t]>;
my $fws = qq<(?:(?:$wsp*\\015\\012)?$wsp+)>;
# our regexes for control characters
my $no_ws_ctl = q<\x01-\x08\x0B\x0C\x0E-\x1F\x7F>;
# regex for "text", any ascii character other than a CR or LF
my $text = q<[\x01-\x09\x0B\x0C\x14-\x7F]>;
#regexes for "atoms"
#define our atomtext
my $atext = q<[!#$%&'*+\-/=?^`{|}~\w]>;
# an atom is atext optionally surrounded by folded white space
my $atom = qq<(?:$fws*$atext+$fws*)>;
# a dotatom is atom text optionally followed by a dot and more atomtext
my $dotatomtext = qq<(?:$atext+(?:\\.$atext+)*)>;
#a dotatom is dotatomtext optionally surrounded by folded whitespace
my $dotatom = qq<(?:$fws?$dotatomtext$fws?)>;
#a quoted pair is a backslash followed by a single text character, as defined above.
my $quoted_pair = '(?:' . q<\\> . qq<$text> . ')';
#regexes for quoted strings
#quoted text is text between quotes, it can be any control character,
#in addition to any ASCII character other than \ or "
my $qtext = '(?:' . '[' . $no_ws_ctl . q<\x21\x23-\x5B\x5D-\x7E> . ']' . ')';
#content inside a quoted string may either be qtext or a quoted pair
my $qcontent = qq<(?:$qtext|$quoted_pair)>;
#and, finally, our quoted string is optional folded white space, then a double quote
#with as much qcontent as we'd like (optionally surrounded by folding white space
#then another double quote, and more optional folded white space
my $quoted_string = qq<(?:$fws?"(?:$fws?$qcontent)*$fws?"$fws?)>;
#a word is an atom or a quoted string
my $word = qq<(?:$atom|$quoted_string)>;
#a phrase is multiple words
my $phrase = qq<$word+>;
#the local part of an address is either a dotatom or a quoted string
my $local_part = qq<(?:$dotatom|$quoted_string)>;
#regexes for domains
# #domain text may be a control character, in addition to any ASCII character other than [, \, or ]
# my $dtext = '(?:' . '[' . $no_ws_ctl . q<\x21-\x5A\x5E-\x7E> . ']' . ')';
#
# #domain content is either dtext or a quoted pair
# my $dcontent = qq<(?:$dtext|$quoted_pair)>;
#
# #a domain literal is optional folded white space, followed by a literal [
# #then optional folded white space and arbitrary dcontent, followed by another literal ]
# #and then optional fws
# my $domain_literal = qq<(?:$fws?\\[(?:$fws?$dcontent)*\\]$fws)>;
#
# #and, finally, a domain is either a dotatom or a domainliteral.
# my $domain = qq<(?:$dotatom|$domain_literal)>;
# RFC 2821 is a bit stricter than RFC 2822. In fact, according to that document, a domain may be only
# letters, numbers, and hyphens. Go figure. I kept the old domain specification in the comments
# immediately above here, just 'cuz I was so proud of 'em. :)
my $domain = q<[a-zA-Z0-9\-]+(?:\.[a-zA-Z0-9\-]+)*\\.(?:[a-zA-Z][a-zA-Z](?:[a-zA-Z](?:[a-zA-Z](?:[a-zA-Z][a-zA-Z])?)?)?)>;
#our address spec. Defines user@domain.com
#note - very important, that the addr_spec is within backtracking parentheses. This value will
#go into either $1 (common) or $2 (not quite as common).
#also note that we deviate from RFC 2822 here, by forcing the TLD of 2,3,4 or 6 characters.
#that's what the internet uses, regardless of what the spec allows.
my $addr_spec = '(' . $local_part . '@' . $domain . ')';
#a display name (displayname<addr_spec>) is just a phrase
my $display_name = $phrase;
#an angle_addr is just an addr_spec surrounded by < and >, with optional folded white space
#around that
my $angle_addr = qq[(?:$fws?<$addr_spec>$fws?)];
#a name address is an optional display_name followed by an angle_addr
my $name_addr = qq<(?:$display_name?$angle_addr)>;
# and a mailbox is either an addr_spec or a name_addr
# the mailbox is our final regex that we use in valid_email
#
my $mailbox = qq<(?:$addr_spec|$name_addr)>;
#
##
# a mailbox list is, as it sounds, a list of at least one mailbox, with as many as you'd like, comma delimited
my $mailbox_list = qq<(?:$mailbox(?:,$mailbox)*)>;
# and a group is a display_name, a :, and an optional mailbox list, ended with a semi-colon
# This is used in the To accessor, which is allowed to contain groups.
my $group = qq<(?:$display_name:(?:$mailbox_list|$fws)?;)>;
=pod
=item valid_email
valid_email validates an email address and extracts the user@domain.com part of an address
print $bulk->valid_email('jim@jimandkoka.com')->{'extracted'}; #prints jim@jimandkoka.com
print $bulk->valid_email('"Jim Thomason"<jim@jimandkoka.com>')->{'extracted'}; #prints jim@jimandkoka.com
print $bulk->valid_email('jim@jimandkoka.com')->{'extracted'}; #prints jim@jimandkoka.com
print $bulk->valid_email('jim@@jimandkoka.com'); #prints nothing (invalid address)
Note that as of v3.10, valid_email returns a hash with two keys upon success. 'original' contains the address as you
passed it in, 'extracted' is the address person that was yanked out.
{
'original' => 'Jim Thomason'<jim@jimandkoka.com',
'extracted' => 'jim@jimandkoka.com',
}
Given an invalid address, returns undef and sets an error as always.
If Trusting is 1, then valid_email only removes comments and extracts the address spec part of the email. i.e., if your address is
some name<some@address.com>
It'll just return some@address.com. This is required, because valid_email is also where the address spec is validated.
As of 3.00, valid_email should be fully RFC 2822 compliant, except where otherwise noted (such as forcing a valid domain as per RFC 2821).
And also as of 3.00, Trusting is even more trusting and has a faster return. There are speed reasons to have Trusting set
to 1 (such as not having to check the validity of each email address), but if you do that then you must be B<positive> that
B<all> of your addresses are 100% valid. If you have B<any> addresses in your list that are invalid and Trusting is set to 1,
then you may have bad things happen. You have been warned.
This method is known to be able to return:
MB006 - no email address
MB007 - invalid email address
=cut
sub valid_email {
my $self = shift;
my $email = shift;
my $allow_groups = shift;
my $return_hash = {
'original' => $email
};
return $self->error("Cannot validate w/o email address", "MB006") unless $email;
$email = $self->_comment_killer($email); #No one else handles comments, to my knowledge. Cool, huh? :)
# if we're trusting, trivially extract the address-spec and return it
if ($self->Trusting('email')){
$email =~ s/.+<(.+)>/$1/g;
$return_hash->{'extracted'} = $email;
return $return_hash;
};
#okay, check our email address
if ($email =~ m!^$mailbox$!o){
$return_hash->{'extracted'} = $1 || $2; #our address could be in either place;
return $return_hash;
}
#if it fails as an email address and we allow groups, see if we were passed a group
elsif ($allow_groups && $email =~ m!^$group$!o){
#the $group regex can't extract emails, so we'll just return the whole thing.
$return_hash->{'extracted'} = $email;
return $return_hash;
}
#finally, otherwise give an error
else {
$self->logToFile($self->BAD, \$email);
return $self->error("Invalid email address : $email", "MB007");
};
};
# _comment_killer is used internally by valid_email, _comment_killer does what you'd expect from it, it removes
# comments from email addresses
sub _comment_killer {
my $self = shift;
my $email = shift;
#comment text is anything in ASCII, except for \, (, and )
my $ctext = '(' . '[' . $no_ws_ctl . q<\x21-\x27\x2A-\x5B\x5D-\x7E> . ']' . ')';
#the content of a comment is either ctext or a quoted pair
#we are deviating from RFC 2822, because comments can nest arbitrarily. But we don't allow that.
my $ccontent = qq<($ctext|$quoted_pair)>; #|$comment, but we don't allow nesting here
#and finally, a comment is a ( followed by arbitrary ccontent, followed by another )
my $comment = '(' . '\(' . qq<($fws?$ccontent)*$fws?> . '\)' . ')';
while ($email =~ /$comment/o){$email =~ s/$comment//go};
return $email;
};
};
# _valid_precedence is used internally to check whether a precedence is valid, i.e., list, bulk, or junk
# It is called by the Precedence wrapper to the _Precedence attribute
sub _valid_precedence {
my $self = shift;
my $value = shift;
if ($self->Trusting('precedence') || (defined $value && $value =~ /list|bulk|junk/i)){
return 1;
} else {
$value = '' unless defined $value;
return $self->error("Invalid precedence ($value) : only 'list', 'bulk', or 'junk'", "MB008");
};
};
#/validation
#now, for the methods
=pod
=item lc_domain
given an email address, lowercases the domain. Mainly used internally, but I thought it might be useful externally as well.
print $self->lc_domain('Jim@JimANDKoka.com'); #prints Jim@jimandkoka.com
print $self->lc_domain('JIM@JIMANDKOKA.com'); #prints JIM@jimandkoka.com
print $self->lc_domain('jim@jimandkoka.com'); #prints jim@jimandkoka.com
This method is known to be able to return:
MB009 - cannot lowercase domain w/o email
=cut
sub lc_domain {
#lowercase the domain part, but _not_ the local part. Why not?
#Read the specs, you can't make assumptions about the local part, it is case sensitive
#even though 99.999% of the net treats it as insensitive.
my $self = shift;
my $email = shift || return $self->error("Cannot lowercase domain with no email address", "MB009");
(my $lc = $email) =~ s/^(.+)@(.+)$/$1@\L$2/;
return $lc;
};
=pod
=item setDuplicate
sets an email address as a duplicate.
$bulk->setDuplicate($email);
once an address is set as a duplicate, then isDuplicate will return a true value for that address
print $bulk->isDuplicate($email2); #prints 0
$bulk->setDuplicate($email2);
print $bulk->isDuplicate($email2); #prints 1
This is mainly used internally, but I decided to make it external anyway.
setDuplicate will always return 1 if you have Trusting('duplicates') set.
Be warned that there is a performance hit to using this, since it will eventually store your entire list inside an
entire hashref in memory. You're in much better shape if you weed out the duplicates in advance and then set Trusting('duplicates' => 1)
to skip the check and skip storing the values in the hashref.
But if you have to use this to weed out values, go to town.
This method is known to be able to return:
MB010 - cannot set duplicate w/o email
=cut
sub setDuplicate {
my $self = shift;
my $email = shift || return $self->error("Cannot set duplicate without email", "MB010");
return 1 if $self->Trusting('duplicates');
if (! $self->Trusting('banned')) {
$self->_duplicates->{lc $email} = 1;
}
else {
$self->_duplicates->{$self->lc_domain($email)} = 1;
};
return 1;
};
=pod
=item isDuplicate
returns a boolean value as to whether an email address is a duplicate
print $bulk->isDuplicate($email); #prints 0 or 1
once an address is set as a duplicate, then isDuplicate will return a true value for that address
print $bulk->isDuplicate($email2); #prints 0
$bulk->setDuplicate($email2);
print $bulk->isDuplicate($email2); #prints 1
This is mainly used internally, but I decided to make it external anyway.
isDuplicate will always return 0 if you have Trusting('duplicates' => 1) set.
Be warned that there is a performance hit to using this, since it will eventually store your entire list inside an
entire hashref in memory. You're in much better shape if you weed out the duplicates in advance and then set Trusting('duplicates' => 1)
to skip the check and skip storing the values in the hashref.
But if you have to use this to weed out values, go to town.
=cut
sub isDuplicate {
my $self = shift;
my $email = shift || return $self->undef("Cannot check duplicate without email", "MB015");
return 0 if $self->Trusting('duplicates');
if (! $self->Trusting('banned')){
return $self->_duplicates->{lc $email};
}
else {
return $self->_duplicates->{$self->lc_domain($email)};
};
};
=pod
=item isBanned
returns a boolean value as to whether an email address (or domain) is banned or not
$bulk->isBanned($email); #prints 0 or 1
$bulk->isBanned($domain); #prints 0 or 1
->isBanned goes off of the values populated via the banned attribute
This is mainly used internally, but I decided to make it external anyway.
=cut
sub isBanned {
my $self = shift;
my $email = shift || return $self->undef("Cannot check banned-ness without email", "MB016");
(my $domain = $email) =~ s/^.+@//;
return 2 if $self->banned->{lc $domain};
if (! $self->Trusting('banned')){
return $self->banned->{lc $email};
}
else {
return $self->banned->{$self->lc_domain($email)};
};
};
=pod
=item nextServer
Again, mainly used internally.
->nextServer will iterate over the ->servers array and return the next valid, connected server. If a server is
not connected, ->nextServer will try to make it connect. If the server cannot connect, it will go on to the next one.
Once all servers are exhausted, it returns undef.
nextServer is called if the present server object has reached one of its internal limits. See Mail::Bulkmail::Server for more
information on server limits.
This method is known to be able to return:
MB011 - No servers (->servers array is empty)
MB012 - No available servers (cannot connect to any servers)
=cut
sub nextServer {
my $self = shift;
return $self->error("No servers", "MB011") unless $self->servers && @{$self->servers};
my $old_idx = $self->_server_index;
my $new_idx = ($old_idx + 1) % @{$self->servers};
#special case for loop prevention. Internally, we initially start @ -1, to start off at 0 instead of 1.
$old_idx = 0 if $new_idx == 0;
while (1){
#prevent infinite loops. If we get back to the beginning AND that server is worthless ("not not worthless"), then
#we can't connect to any of 'em.
if ($new_idx == $old_idx && ! $self->servers->[$new_idx]->_not_worthless){
return $self->error("No available servers", "MB012");
}
else {
#if we're connected, we're golden.
if ($self->servers->[$new_idx]->connected){
$self->_server_index($new_idx);
return $self->servers->[$new_idx];
}
#otherwise, try to connect
else {
$self->servers->[$new_idx]->connect;
#if we succeed, we're golden
if ($self->servers->[$new_idx]->connected){
$self->_server_index($new_idx);
return $self->servers->[$new_idx];
}
}
};
#otherwise, no matter what, if we're down here we want to look at the next server in the list
$new_idx = ($new_idx + 1) % @{$self->servers};
};
};
=pod
=item extractEmail
The extract methods return results equivalent to the return of valid_email
extracts the email address from the data passed in the bulkmail object. Not necessary in Mail::Bulkmail, since all it
does in here is reflect through the same value that is passed.
This will be very important in a subclass, though. getNextLine might return values beyond just simple email addresses
in subclasses, hashes, objects, whatever. You name it. In that case, extractEmail is necessary to find the actual email
address out of whatever it is that was returned from getNextLine().
But here? Nothing to worry about.
This method is known to be able to return:
MB013 - cannot extract email w/o email
=cut
sub extractEmail {
my $self = shift;
my $email = shift || return $self->error("Cannot extract email w/o email", "MB013");
return $self->valid_email($$email);
};
=pod
=item extractSender
The extract methods return results equivalent to the return of valid_email
extracts the sender of the message from the data passed in the bulkmail object. Not necessary in Mail::Bulkmail, since
all it does in here is return either the Bulkmail object's Sender or its From field.
This will be very important in a subclass, though. getNextLine might return values beyond just simple email addresses
in subclasses - hashes, object, whatever. You name it. In that case, extractEmail is necessary to find the actual email
address out of whatever it is that was returned from getNextLine().
But here? Nothing to worry about.
=cut
sub extractSender {
my $self = shift;
#we cheat like a madman in this method. We -know- that the Sender and the From are valid, since we validated
#them before they're insered. So we do the trivial extract and return that way.
my $sender = $self->Sender || $self->From;
my $return_hash = {'original' => $sender};
$sender =~ s/.+<(.+)>/$1/g;
$return_hash->{'extracted'} = $sender;
return $return_hash;
};
=pod
=item extractReplyTo
The extract methods return results equivalent to the return of valid_email
extracts the Reply-To of the message from the data passed in the bulkmail object. Not necessary in Mail::Bulkmail, since
all it does in here is return either the Bulkmail object's Sender or its From field.
This will be very important in a subclass, though. getNextLine might return values beyond just simple email addresses
in subclasses - hashes, object, whatever. You name it. In that case, extractEmail is necessary to find the actual email
address out of whatever it is that was returned from getNextLine().
But here? Nothing to worry about.
=cut
sub extractReplyTo {
my $self = shift;
#we cheat like a madman in this method. We -know- that the Sender and the From are valid, since we validated
#them before they're insered. So we do the trivial extract and return that way.
my $replyto = $self->ReplyTo || $self->From;
my $return_hash = {'original' => $replyto};
$replyto =~ s/.+<(.+)>/$1/g;
$return_hash->{'extracted'} = $replyto;
return $return_hash;
};
=pod
=item preprocess
This is another method that'll do more in a subclass. When you had off data to either ->mail or ->bulkmail,
it gets preprocessed before it's actually used. In Mail::Bulkmail itself, all it does is take a non-reference
value and turn it into a reference, or return a reference as is if that was passed.
Here, the whole method:
sub preprocess {
my $self = shift;
my $val = shift;
return ref $val ? $val : \$val;
};
But in a subclass, this may be much more important. Making sure that your data is uniform or valid, that
particular values are populated, additional tests, whatever.
=cut
sub preprocess {
my $self = shift;
my $val = shift;
return ref $val ? $val : \$val;
};
# _force_wrap_string is an internal method that handles wrapping lines as appropriate, either to 80 characters per line
# if ->force80 is true, and otherwise to 1000 characters to comply with RFC2822. Will not touch the string
# if Trusting is set to 1.
#
# though this is re-written, I'm still not terribly thrilled with it.
sub _force_wrap_string {
my $self = shift;
my $string = shift;
my $spaceprepend= shift || 0;
my $noblanks = shift || 0;
#if we're trusting the wrap, just return the string
return $string if $self->Trusting('wrapping');
#determine the length we wrap to
my $length = $self->force80 ? 78 : 998;
#if we're tacking a space on to the front, that's an extra character, so decrement the length to match
$length-- if $spaceprepend;
#we want to split into as many fields as there are returns in the message
my @returns = $string =~ m/(\015\012)/g;
my @lines = split(/\015\012/, $string, scalar @returns);
foreach (@lines){
if (length $_ > $length){
my $one = 0;
# boy, did this take finesse. Only prepend a space if it's not the start of the original line
# That way, we can properly wrap our headers. That's what $one is.
# this regex puts as many characters before a wordbreak as it can into $1, and the rest into $2.
# if a string is a solid word greater than the the length, it all goes into $2
$_ =~ s/(?:([^\015\012]{1,$length})\b)?([^\015\012]+)/$self->_process_string($1, $2, $length, $spaceprepend && ! $one++ ? 1 : 0)/ge;
};
};
#rebuild our string
$string = join("\015\012", @lines);
#get rid of any blank lines we may have created, if so desired.
if ($noblanks){
$string =~ s/\015\012[^\015\012\S]*\015\012/\015\012/g while $string =~ /\015\012[^\015\012\S]+\015\012/;
};
return $string;
};
# process string is used internally by _force_wrap_string to do wrapping, as appropriate.
sub _process_string {
my $self = shift;
my $one = shift || ''; #$1, passed from _force_wrap_string
my $two = shift || ''; #$2, passed from _force_wrap_string
my $length = shift; #the length we're wrapping to
my $spaceprepend = shift || 0; #whether we're prepending a space
#re-define the spaceprepend to the character we will prepend.
$spaceprepend = $spaceprepend ? ' ' : '';
#if we don't have $1, then we have a single word greater than the length. Cut it up at the length point, globally
if (! $one){
$two =~ s/([^\015\012]{$length})/$1\015\012$spaceprepend/g;
return $two;
}
#otherwise, use the same regex that _force_wrap_string uses and proceed recusively.
else {
$two =~ s/(?:([^\015\012]{1,$length})\b)?([^\015\012]+)/$self->_process_string($1, $2, $length, $spaceprepend)/ge;
return "$one\015\012$spaceprepend$two";
}
};
=pod
=item buildHeaders
buildHeaders is mainly used internally, like its name implies, it builds the headers for the message.
You'll never need to call buildHeaders unless you're subclassing, in which case you may want to override this method
with a new routine to build headers in a different fashion.
This method is called internally by ->bulkmail and ->mail otherwise and is not something you need to worry about.
The first time buildHeaders is called, it populates _cached_headers so as not to have to go through the processing of rebuilding
the headers for each address in your list.
This method is known to be able to return:
MB014 - no From address
MB015 - no To address
=cut
sub buildHeaders {
my $self = shift;
my $data = shift;
my $headers_hash = shift || $self->_headers;
if ($self->use_envelope && $self->_cached_headers){
return $self->_cached_headers;
}
elsif ($self->_cached_headers){
my $headers = ${$self->_cached_headers};
my $extracted_emails = $self->extractEmail($data);
my $email = $extracted_emails->{'original'};
$headers =~ s/^To: ##EMAIL##/To: $email/m;
return \$headers;
};
my $headers = undef;
$headers .= "Date: " . $self->Date . "\015\012";
if (my $from = $self->From){
$headers .= "From: " . $from . "\015\012";
}
else {
return $self->error("Cannot bulkmail...no From address", "MB014");
};
$headers .= "Subject: " . $self->Subject . "\015\012" if defined $self->Subject && $self->Subject =~ /\S/;
#if we're using the envelope, then the To: header is the To attribute
if (my $to = $self->use_envelope ? $self->To : "##EMAIL##"){
$headers .= "To: $to\015\012";
}
else {
return $self->error("Cannot bulkmail...no To address", "MB015");
};
my $sender_hash = $self->extractSender($data);
if (defined $sender_hash) {
$headers .= "Sender: " . $sender_hash->{'original'} . "\015\012";
}
my $reply_to_hash = $self->extractReplyTo($data);
if (defined $reply_to_hash) {
$headers .= "Reply-To: " . $reply_to_hash->{'original'} . "\015\012";
};
#we're always going to specify at least a list precedence
$headers .= "Precedence: " . ($self->Precedence || 'list') . "\015\012";
if ($headers_hash->{"Content-type"}){
$headers .= "Content-type: " . $headers_hash->{"Content-type"} . "\015\012";
}
else {
if ($self->HTML){
$headers .= "Content-type: text/html\015\012";
}
else {
$headers .= "Content-type: text/plain\015\012";
};
};
foreach my $key (keys %{$headers_hash}) {
next if $key eq 'Content-type';
my $val = $headers_hash->{$key};
next if ! defined $val || $val !~ /\S/;
$headers .= $key . ": " . $val . "\015\012";
};
# I'm taking credit for the mailing, dammit!
$headers .= "X-Bulkmail: " . $Mail::Bulkmail::VERSION . "\015\012";
$headers = $self->_force_wrap_string($headers, 'start with a blank', 'no blank lines');
$headers .= "\015\012"; #blank line between the header and the message
$self->_cached_headers(\$headers);
unless ($self->use_envelope){
my $h = $headers; #can't just use $headers, we'll screw up the ref in _cached_headers
my $extracted_emails = $self->extractEmail($data);
my $email = $extracted_emails->{'original'};
$h =~ s/^To: ##EMAIL##/To: $email/m;
return \$h;
};
return \$headers;
};
=pod
=item buildMessage
buildMessage is mainly used internally, like its name implies, it builds the body of the message
You'll never need to call buildMessage unless you're subclassing, in which case you may want to override this method
with a new routine to build your message in a different fashion.
This method is called internally by ->bulkmail and ->mail otherwise and is not something you need to worry about.
This method is known to be able to return:
MB016 - ->Message is not defined
=cut
sub buildMessage {
my $self = shift;
my $data = shift;
#if we've cached the message, then return it
return $self->_cached_message if $self->_cached_message && $self->_current_message;
#otherwise, use the Message, cache that and return it.
my $message = $self->Message()
|| return $self->error("Cannot build message w/o message", "MB016");
return $message if ref $message;
#sendmail-ify our line breaks
$message =~ s/(?:\r?\n|\r\n?)/\015\012/g;
$message = $self->_force_wrap_string($message);
#double any periods that start lines
$message =~ s/^\./../gm;
#and force a CRLF at the end, unless one is already present
$message .= "\015\012" unless $message =~ /\015\012$/;
$message .= ".";
$self->_cached_message(\$message);
return \$message;
};
=pod
=item bulkmail
This is the bread and butter of the whole set up, and it's easy as pie.
$bulk->bulkmail();
will take your list, iterate over it, build all your message headers, build your message, and email to everyone on your
list, iterating through all of your servers, log all relevant information, and send you happily on your way.
Easy as pie. You don't even need to worry about it if you subclass things, because you'd just need to override
buildHeaders, buildMessage, getNextLine and extractEmail at most.
This method is known to be able to return:
MB017 - duplicate email
MB018 - banned email
MB019 - invalid sender/from
=cut
sub bulkmail {
my $self = shift;
my $server = $self->nextServer || return undef;
my $last_data = undef;
while (defined (my $data = $self->getNextLine)){
if (my $r = $server->reached_limit){
#if a message is waiting on the previous server, then finish it off
if ($self->_waiting_message) {
my $headers = $self->buildHeaders($last_data);
my $message = $self->buildMessage($last_data);
# it is *imperative* that we only send DATA if we have the headers and message body.
# otherwise, the server will hang.
if ($headers && $message) {
my $rc = $server->talk_and_respond("DATA");
$server->talk_and_respond($$headers . $$message) if $rc;
}
my $extracted_emails = $self->extractEmail($last_data);
if (defined $extracted_emails) {
$self->setDuplicate($extracted_emails->{'extracted'});
};
};
$server = $self->nextServer || return undef;
#new server, so nothing should be waiting, and there are no cached domains
$self->_waiting_message(0);
$self->_cached_domain(undef);
#and reset that server's counters
$server->reset_message_counters();
};
$data =~ s/(?:^\s+|\s+$)//g unless ref $data;
$data = $self->preprocess($data) || next;
my $extracted_emails = $self->extractEmail($data) || next;
my $email = $extracted_emails->{'extracted'};
#check for duplicates or banned addresses
if ($self->isDuplicate($email)){
$self->logToFile($self->BAD, $data) if $self->BAD;
$self->error("Invalid email address $email : duplicate", "MB017");
next;
}
elsif (my $b = $self->isBanned($email)){
$self->logToFile($self->BAD, $data) if $self->BAD;
$self->error("Invalid email address $email : " . ($b == 2 ? 'banned domain' : 'banned address'), "MB018");
next;
};
#use the envelope, if we're using it
if ($self->use_envelope){
#extract the domain from the email address
(my $domain = lc $email) =~ s/^[^@]+@//;
#first, see if this is a new domain, either the first time through, if it's a different domain than the last
#one we saw, or if we reached the server's envelope limit
if (! $self->_cached_domain || ($self->_cached_domain && $domain ne $self->_cached_domain()) || $server->reached_envelope_limit) {
#if a message is waiting, then finish it off
if ($self->_waiting_message) {
my $headers = $self->buildHeaders($last_data);
my $message = $self->buildMessage($last_data);
# it is *imperative* that we only send DATA if we have the headers and message body.
# otherwise, the server will hang.
if ($headers && $message) {
my $rc = $server->talk_and_respond("DATA");
$server->talk_and_respond($$headers . $$message) if $rc;
}
my $extracted_emails = $self->extractEmail($last_data);
if (defined $extracted_emails) {
$self->setDuplicate($extracted_emails->{'extracted'});
};
$self->_waiting_message(0);
};
#reset our connection, just to be safe
$server->talk_and_respond("RSET") || next;
my $from_hash = $self->extractSender($data)
|| return $self->error("Could not get valid sender/from address", "MB019");
my $from = $from_hash->{'extracted'};
#say who the message is from
$server->talk_and_respond("MAIL FROM:<" . $from . ">") || next;
#now, since we know that we reset and sent MAIL FROM properly, we'll reset our counter
#and cache this domain
#reset that server's envelope counter
$server->reset_envelope_counter();
#so now we want to cache this domain
$self->_cached_domain($domain);
};
#now, we add this email address to the envelope
$server->talk_and_respond("RCPT TO:<" . $email . ">") || next;
#a message is now waiting to be sent
$self->_waiting_message(1);
#make a note of the email address in the log
$self->logToFile($self->GOOD, $data) if $self->GOOD;
#we need to keep track of the last email sent, to finish off the final
#waiting_message at the end.
$last_data = $data;
#and finally, we cache the domain
$self->_cached_domain($domain);
}
#not using the envelope
else {
$self->mail($data, $server) || next;
};
#make a note of this email address
$self->setDuplicate($email);
#and we increment our counters
$server->increment_messages_sent();
};
#if a message is waiting, then finish it off
if ($self->_waiting_message) {
my $headers = $self->buildHeaders($last_data);
my $message = $self->buildMessage($last_data);
# it is *imperative* that we only send DATA if we have the headers and message body.
# otherwise, the server will hang.
if ($headers && $message) {
my $rc = $server->talk_and_respond("DATA");
$server->talk_and_respond($$headers . $$message) if $rc;
}
my $extracted_emails = $self->extractEmail($last_data);
if (defined $extracted_emails) {
$self->setDuplicate($extracted_emails->{'extracted'});
};
$self->_waiting_message(0);
};
return 1;
};
=pod
=item mail
Works the same as ->bulkmail, but only operates on one email address instead of a list.
$bulk->mail('jim@jimandkoka.com');
Sends your Message as defined in ->Message to jim@jimandkoka.com. You can also optionally pass in a server as the second argument.
$bulk->mail('jim@jimandkoka.com', $server);
is the same as above, but relays through that particular server. if you don't pass a server, if tries to bring the next one
in via ->nextServer
->mail wants its first argument to be whatever would be normally returned by a call to ->getNextLine($bulk->LIST); Right now,
that's just a single email address. But that may change in a subclass. So, if you're operating in a subclass, just remember that
you may be able (or required) to pass additional information in your first argument.
This method is known to be able to return:
MB018 - banned email
MB019 - invalid sender/from address
=cut
sub mail {
my $self = shift;
my $data = shift;
my $passed_server = shift;
my $server = $passed_server || $self->nextServer() || return undef;
$data = $self->preprocess($data);
my $extracted_emails = $self->extractEmail($data) || return undef;
my $email = $extracted_emails->{'extracted'};
if (my $b = $self->isBanned($email)){
$self->logToFile($self->BAD, $data) if $self->BAD;
return $self->error("Invalid email address $email : " . ($b == 2 ? 'banned domain' : 'banned address'), "MB018");
};
#reset our connection, just to be safe
$server->talk_and_respond("RSET")
|| return $self->error($server->error, $server->errcode, 'not logged');
my $from_hash = $self->extractSender($data)
|| return $self->error("Could not get valid sender/from address", "MB019");
my $from = $from_hash->{'extracted'};
#say who the message is from
$server->talk_and_respond("MAIL FROM:<" . $from . ">")
|| return $self->error($server->error, $server->errcode, 'not logged');
#now, we add this email address to the envelope
$server->talk_and_respond("RCPT TO:<" . $email . ">")
|| return $self->error($server->error, $server->errcode, 'not logged');
#we build the headers and message body FIRST, to make sure we have them.
#that way, we can never send DATA w/o a message and hang the server
my $headers = $self->buildHeaders($data) || return undef;
my $message = $self->buildMessage($data) || return undef;
$server->talk_and_respond("DATA")
|| return $self->error($server->error, $server->errcode, 'not logged');
$server->talk_and_respond($$headers . $$message) || return undef;
#make a note of the email address in the log
$self->logToFile($self->GOOD, $data) if $self->GOOD;
return $email;
};
1;
__END__
=pod
=back
=head1 FAQ
=over 5
=item So just how fast is this thing, anyway?>
I don't know any more, I don't have access to the same gigantic lists I used to anymore. :~(
But, basically, Really fast. Really stupendously incredibly fast.
The last official big benchmark I ran was with v1.11. That list runs through to completion in about
an hour and 43 minutes, which meant that Mail::Bulkmail 1.11 could process (at least) 884 messages per minute or about
53,100 per hour.
The last message sent out was 4,979 bytes. 4979 x 91,140 people is 453,786,060 bytes of data
transferred, or about 453.786 megabytes in 1 hour and 43 minutes. This is a sustained transfer rate of about 4.4 megabytes
per minute, or 264.34 megabytes per hour.
So then, that tells you how fast the software was back in 1999, 2 major revisions ago. But, invariably, you want to know what it's
like *now*, right? Well, I'll do my best to guesstimate it. However, these tests were not run through an SMTP relay, they were run
using DummyServer in v3.0 and a hacked 2.05 and (severely) hacked 1.11 to insert similar functionality. All data was sent to /dev/null.
Tests were performed on a 5,000 recipient list.
First of all, with envelope sending turned off (average times):
v1.11......20 seconds (1.00)
v3.00......23 seconds (1.15)
v2.05......50 seconds (2.5)
1.11 was the speed champ in this case, but that's not surprising considering the fact that it did a lot less processing than the
other 2. The fact that 3.00 almost catches it should speak to the improvement in the code in the 3.x release. 2.05 was...clunky.
Now then, there's another thing to consider, envelope sending. With envelope sending turned on (average times):
v3.00......12 seconds (1.00)
v2.05......19 seconds (1.58)
v1.11......22 seconds (1.83)
This is with an envelope_limit of 100. So the supposed speed gains that envelope sending were supposed to see in 2.05 never
really materialized. While doing these tests, I discovered a bug in 2.05's use_envelope routine that would sometimes cause it
to slow down substantially. 3.00, with a new routine, was never affected. Incidentally, Bulkmail 2.05 will be faster with trivially
low envelope_limits. Bulkmail 3.00 becomes faster with an envelope_limit greater than 2.
There is also mail merging (filemapping in 1.x) that should be considered. This was benchmarked with Mail::Bulkmail::Dynamic for 3.00.
A simple mail merge with one item was used, and one global item, read from a file, and split on a delimiter (since this was the
only functionality that v1.x had). With mail merge turned on (average times):
v1.11......20 seconds (1.00)
v3.00......35 seconds (1.75)
v2.05......40 seconds (2.00)
And finally, 2.x and 3.x have both had the capability to generate a dynamic message. This is a minimal test with one dynamic
message element, one dynamic header, and a mail merge into the dynamic element:
v3.00......36 seconds (1.00)
v2.05......44 seconds (1.22)
So 3.x is usually faster than 2.x, but sometimes slower than 1.x. Which makes sense, again due to the added features in 2.x and 3.x.
These tests do not take into account the multi-server capability introduced in 3.00.
Also note that these speeds are only measuring the time it takes to get from Mail::Bulkmail to your SMTP relay. There are no
measurements reflecting how long it may take your SMTP relay to send the data on to the recipients on your list.
=item Am I going to see speeds that fast?
Maybe, maybe not. It depends on how busy your SMTP server is. If you have a relatively unused SMTP server with a fair amount
of horsepower and a fast connection, you can easily get these speeds or beyond. If you have a relatively busy and/or low powered
SMTP server or slow connections, you're not going to reach speeds that fast.
=item How much faster will Mail::Bulkmail be than my current system?
This is a very tough question to answer, since it depends highly upon what your current system is. For the sake of argument,
let's assume that for your current system, you open an SMTP connection to your server, send a message, and close the connection.
And then repeat. Open, send, close, etc.
Mail::Bulkmail will I<always> be faster than this approach since it opens one SMTP connection and sends every single message across
on that one connection. How much faster depends on how busy your server is as well as the size of your list. The connection will
only be closed if you have an error or if you reach the max number of messages to send in a given server connection.
Lets assume (for simplicity's sake) that you have a list of 100,000 people. We'll also assume that you have a pretty busy
SMTP server and it takes (on average) 25 seconds for the server to respond to a connection request. We're making 100,000
connection requests (with your old system). That means 100,000 x 25 seconds = almost 29 days waiting just to make connections
to the server! Mail::Bulkmail makes one connection, takes 25 seconds for it, and ends up being 100,000x faster!
But, now lets assume that you have a very unbusy SMTP server and it responds to connection requests in .003 seconds. We're making
100,000 connection requests. That means 100,000 x .0003 seconds = about 5 minutes waiting to make connections to the server.
Mail::Bulkmail makes on connection, takes .0003 seconds for it, and ends up only being 1666x faster. But, even though being
1,666 times faster sounds impressive, the world won't stop spinning on its axis if you use your old system and take up an extra
5 minutes.
And this doesn't even begin to take into account systems that don't open and close SMTP connections for each message.
This also doesn't take into account the load balancing between multiple SMTP relays that 3.00 can perform.
In short, there's no way for me to tell how much faster (if at all) it'll be. Try it and find out.
=item Have you benchmarked it against anything else?
Not scientifically. I've heard that Mail::Bulkmail 1.10 is about 4-5x faster than Listcaster from Mustang Software, but I don't
have any hard numbers. But nothing beyond that.
If you want to benchmark it against some other system and let me know the results, it'll be much appreciated. :-)
=item Can I send spam with this thing?
No. Don't be a jerk.
=item SMTP relay? Wazzat?
All Mail::Bulkmail does is provide you a quick way to relay information from your local machine through to your SMTP relay (which may
be the same machine). Your SMTP relay then sends the messages on to the rest of the world.
So your SMTP server must be configured properly to allow you to relay your messages out. It is recommended that this machine be kept
behind a firewall for security reasons. Make sure that it's configured properly so it's not an open relay. Ask your SysAdmin for help.
=item What about multi-part messages?
Not yet supported. I'll definitely add internal support for multi-part/alternative in the future.
Until then? You can always do the MIME encoding yourself, set your own headers, etc. It's perfectly fine to do it yourself, but you
will have to do it yourself for now.
=item Mail::Bulkmail is really cool, but what'd be even cooler is a front end for the thing! Do you have one of those?
I don't. But check out Mojo Mail:
http://mojo.skazat.com/
Active community, developer, etc. Looks like a good product.
=item You know, you re-invent a lot of wheels.
Yeah, I do. Hey, c'mon, I write this stuff for the fun of it. And that means that I'm going to do it the way that I want to. :)
Besides, I've never had any problem with re-inventing wheels. After all, if the wheel hadn't been re-invented a few times, we'd
still be using solid plain wooden wheels. Not to say that I necessarily think that I've invented better things here than are
available elsewhere, but I might eventually. Who knows.
Anyway, you're more than free to subclass and over-ride things with "standard" modules if you'd like. ou can make your
own server implementation using Net::SMTP, or your own dynamic message system using Text::Template, or whatever else. Feel free
to use the standards if you'd prefer.
Me? I enjoy re-inventing wheels, so I'll continue to do so.
=item Dude! Warnings is on!
That's by design. Nothing in the code ever should generate a warning, but if it does, then please please B<please> let me know
about it so I can patch it. You can always turn off warnings yourself if you're worried/annoyed.
=item So what is it with these version numbers anyway?
I'm going to I<try> to be consistent in how I number the releases.
The B<hundredths> digit will indicate bug fixes, minor behind-the-scenes changes, etc.
The B<tenths> digit will indicate new and/or better functionality, as well as some minor new features.
The B<ones> digit will indicate a major new feature or re-write.
Basically, if you have x.ab and x.ac comes out, you want to get it guaranteed. Same for x.ad, x.ae, etc.
If you have x.ac and x.ba comes out, you'll probably want to get it. Invariably there will be bug fixes from the last "hundredths"
release, but it'll also have additional features. These will be the releases to be sure to read up on to make sure that nothing
drastic has changes.
If you have x.ac and y.ac comes out, you'll want to do research before upgrading. I break things, I lose backwards compatibility,
I change stuff around a lot. Just my nature. Porting from one major release to the next is pretty straightforward, but there's still
work to be done on your part - it won't just be a drop in replacement. And, depending upon your list and what options you're using, you
may or may not see any benefit to upgrading. Read the docs, ask me questions, and test test test.
Don't get me wrong, I'm not going to intentially *try* to make things not backwards compatible, but if I come up with what I think
is a better way of doing things, I'm going to go with it. And I don't like to pollute modules with a lot of cruft bridgeworks for
backwards compatibility. This thing is huge enough as is without having to worry about making sure internal band-aids work.
If this'll be a problem, then don't upgrade.
=item Is anything missing vs. the old versions?
Yes. You can't currently extract headers from the message you're sending. This will return in the future, probably.
When using dynamic_header_data, you can no longer set a default header to be used if no header is defined for the individual user.
This will also probably return in the future.
local merges no longer exist. You only have global merges and individual ones.
It will now date all messages to the time of the first sent message.
You can no longer externally load in a list of duplicates. Come on, did *anybody* ever actually do that?
=item When I try to bulkmail, I get an error that says "Cannot bulkmail...no To address" How do I fix this?
Ya know, I B<thought> this error was self-explanatory, but considering the number of people that email me
about it, I guess it's not.
The issue here is that (say it with me now), you can't bulkmail because the To header hasn't been set.
If you're using envelope sending (on by default in Mail::Bulkmail), then you have to specify an address
to set in the To: header of the message. This is specified via the ->To accessor.
$bulk->To("mylist@mysite.com");
So, specify the To header, and then you'll be fine.
=item Wow, this module is really cool. Have you contributed anything else to CPAN?
Yes, Carp::Notify and Text::Flowchart
=item Was that a shameless plug?
Why, yes. Yes it was.
=item Anything else you want to tell me?
Sure, anything you need to know. Just drop me a message.
=back
=head1 EXAMPLES
#simple mailing with a list called "./list.txt"
my $bulk = Mail::Bulkmail->new(
"LIST" => "./list.txt",
"Subject" => "A test message",
"Message" => "This is my test message",
"From" => 'me@mydomain.com',
"To" => 'somelist@mydomain.com',
"Reply-To" => 'replies@mydomain.com'
) || die Mail::Bulkmail->error();
$bulk->bulkmail || die $bulk->error;
#same thing, but turning off envelope sending
my $bulk = Mail::Bulkmail->new(
"LIST" => "./list.txt",
"Subject" => "A test message",
"Message" => "This is my test message",
"From" => 'me@mydomain.com',
"Reply-To" => 'replies@mydomain.com',
"use_envelope" => 0
) || die Mail::Bulkmail->error();
$bulk->bulkmail || die $bulk->error;
#Small example, with a miniature in memory list
my $bulk = Mail::Bulkmail->new(
"LIST" => [qw(test@mydomain.com me@mydomain.com test2@mydomain.com)],
"Subject" => "A test message",
"Message" => "This is my test message",
"From" => 'me@mydomain.com',
"To" => 'somelist@mydomain.com',
"Reply-To" => 'replies@mydomain.com',
"Sender" => 'sender@mydomain.com'
) || die Mail::Bulkmail->error();
$bulk->bulkmail || die $bulk->error;
#Make sure our error logging is on in a different place, and set up a different server
my $server = Mail::Bulkmail::Server->new(
'Smtp' => "smtp.mydomain.com",
"Port" => 25
) || die Mail::Bulkmail::Server->error();
my $bulk = Mail::Bulkmail->new(
"LIST" => "./list.txt",
"Subject" => "A test message",
"Message" => "This is my test message",
"From" => 'me@mydomain.com',
"To" => 'somelist@mydomain.com',
"Reply-To" => 'replies@mydomain.com',
"ERRFILE" => '/etc/mb/error.file.txt',
"servers" => [$server] #our new server
) || die Mail::Bulkmail->error();
$bulk->bulkmail || die $bulk->error;
#Make sure our error logging is on in a different place, and set up a different server
#this time, we'll use a dummy server for debugging purposes
my $dummy_server = Mail::Bulkmail::DummyServer->new(
"dummy_file" => "/etc/mb/dummy.server.output.txt"
) || die Mail::Bulkmail::DummyServer->error();
my $bulk = Mail::Bulkmail->new(
"LIST" => "./list.txt",
"Subject" => "A test message",
"Message" => "This is my test message",
"From" => 'me@mydomain.com',
"To' => 'somelist@mydomain.com',
"Reply-To" => 'replies@mydomain.com',
"ERRFILE" => '/etc/mb/error.file.txt',
"servers" => [$dummy_server] #our new server, which is a dummy server
) || die Mail::Bulkmail->error();
$bulk->bulkmail || die $bulk->error;
#mailing just to one address
my $bulk = Mail::Bulkmail->new(
"Subject" => "A test message",
"Message" => "This is my test message",
"From" => 'me@mydomain.com',
"Reply-To" => 'replies@mydomain.com',
"Sender" => 'sender@mydomain.com'
) || die Mail::Bulkmail->error();
$bulk->mail('test@yourdomain.com') || die $bulk->error;
#here, a fun one. Use a coderef as our LIST
my $query = "select email, domain from table order by domain";
my $stmt = $dbh->prepare($query) || die;
$stmt->execute || die;
sub get_list {
my $bulk = shift; #we always get our bulkmail object first
my $data = $stmt->fetchrow_hashref();
if ($data) {
return $data->{"email"};
}
else {
return undef;
};
};
$bulk->LIST(\&get_list);
#and now, logging to a coderef.
my $query = ('insert into table good_addresses (email) values (?)');
my $stmt = $dbh->prepare($query) || die;
sub store_to_db {
my $bulk = shift; #always get our bulkmail object first
my $email = shift;
$stmt->execute($email) || return $bulk->error("Could not store to DB!");
return 1;
};
$bulk->GOOD(\&store_to_db);
=head1 SAMPLE CONFIG FILE
This is my current conf file. It's about as close to one that you want to use as possible. Remember, you
can set any values you'd like in the conf file, as long as they're scalars or arrayrefs of scalars. For example, if you
want a default "From" value, then define it in the conf file.
For more information on conf files, see Mail::Bulkmail::Object. For more information on the server file, see
Mail::Bulkmail::Server. This file is also stored in the file "sample.cfg.file"
define package Mail::Bulkmail
#server_class stores the server object that we're going to use.
#uncomment the DummyServer line and comment out the Server line for debugging
server_class = Mail::Bulkmail::Server
#server_class = Mail::Bulkmail::DummyServer
#log our errors
ERRFILE = /etc/mb/error.txt
BAD = /etc/mb/bad.txt
GOOD = /etc/mb/good.txt
banned = /etc/mb/banned.txt
#if we want a default From value, you can place it here.
#From = me@mydomain.com
define package Mail::Bulkmail::Server
#set up the domain we use to say HELO to our relay
Domain = mydomain.com
#Most servers are going to connect on port 25, so we'll set this as the default port here
Port = 25
#We'll give it 5 tries to connect before we let ->connect fail
Tries = 5
#Lets try to reconnect to a server 5 times if ->connect fails.
max_connection_attempts = 5
#100 is a good number for the envelope_limit
envelope_limit = 100
#Send 1,000 messages to each server in the round before going to the next one.
#set max_messages_per_robin to 0 if you're only using one server, otherwise you'll have needless
#overhead
max_messages_per_robin = 0
#maximum number of messages per connection. Probably best to keep this 0 unless you have a reason
#to do otherwise
max_messages_per_connection = 0
#maximum number of messages for the server. Probably best to keep this 0 unless you have a reason
#to do otherwise
max_messages= 0
#maximum number of messages to send before sleeping, probably best to keep this 0 unless you need
#to let your server relax and sleep
max_messages_while_awake = 0
#sleep for 10 seconds if we're sleeping. This line is commented out because we don't need it.
#No harm in uncommenting it, though.
#sleep_length = 10
#our list of servers
server_file = /etc/mb/servers.txt
define package Mail::Bulkmail::Dynamic
#it is highly recommended that quotemeta be 1
quotemeta = 1
#set up our default delimiters
dynamic_message_delimiter = ;
dynamic_message_value_delimiter = =
dynamic_header_delimiter = ;
dynamic_header_value_delimiter = =
#we're going to assume that duplicates have been weeded out, so we'll allow them.
Trusting @= duplicates
#By default, we'll turn on our envelope. Mail::Bulkmail might as well use it.
#Mail::Bulkmail::Dynamic doesn't care about this value.
use_envelope = 1
define package Mail::Bulkmail::DummyServer
#Our dummy data file, for when we're using DummyServer. It's also useful to send the data to
#/dev/null to test things if you don't care about the message output.
dummy_file = /etc/mb/dummy.file
#dummy_file = /dev/null
=head1 DIAGNOSTICS
Bulkmail doesn't directly generate any errors. If something fails, it will return undef
and set the ->error property of the bulkmail object. If you've provided an error log file,
the error will be printed out to the log file.
Check the return of your functions, if it's undef, check ->error to find out what happened.
Be warned that isDuplicate and isBanned will return 0 if an address is not a duplicate or banned, respectively,
but this is not an error condition.
=head1 SEE ALSO
Mail::Bulkmail::Object, Mail::Bulkmail::Server, Mail::Bulkmail::Dummy
=head1 COPYRIGHT (again)
Copyright and (c) 1999, 2000, 2001, 2002, 2003 James A Thomason III (jim@jimandkoka.com). All rights reserved.
Mail::Bulkmail is distributed under the terms of the Perl Artistic License.
=head1 CONTACT INFO
So you don't have to scroll all the way back to the top, I'm Jim Thomason (jim@jimandkoka.com) and feedback is appreciated.
Bug reports/suggestions/questions/etc. Hell, drop me a line to let me know that you're using the module and that it's
made your life easier. :-)
http://www.jimandkoka.com/jim/perl/ for more perl info, http://www.jimandkoka.com in general
=cut
|