Newsgroup: it.comp.www.php
Titolo: mail() fallisce solo se tutti i destinatari vengono respinti?

Sto esplorando l'affidabilità di mail() per l'invio "massivo" a due o
più destinatari. Sebbene il manuale non lo dica, sembra che:
 
1. Non si può avere retroazione del motivo del fallimento.
 
2. Se fallisce, nessun messaggio viene inviato.
 
3. Sa ha successo, QUALCUNO dei destinatari potrebbe ricevere il
messaggio.
 
4. Se c'è un solo destinatario (To, Cc e Bcc) e questo viene respinto
dal server SMTP, allora mail() fallisce.
 
5. Se ci sono due o più destinatari (To, Cc e Bcc) e almeno uno di
questi viene accettato, allora mail() ha successo; non c'è modo di
sapere quali destinatari sono stati respinti se non andando a vedere i
log files del server o aspettanto i bounce.
 
Test code minimalista delle varie combinazioni che compila il campo To
(ma con Cc e Bcc sembra che le cose vadano nello stesso modo):
 
<?php
error_reporting(-1);
echo "PHP version: ", PHP_VERSION, "\n";
$good = "
te...@icosaedro.it
";
$bad  = "<<<>>>";
$unknown = "DoesNotExist@DoesNotExists";
 
$attempts = [
        $good,
        $bad,
        $unknown,
        "$good, $bad",
        "$good, $unknown",
        "$bad, $unknown"
];
 
foreach($attempts as $to){
        echo "Sending to $to:\n";
        var_dump( mail($to, "This message sent to $to", "Testing.") );
        var_dump( error_get_last() );
}
?>
 
Esito del codice precedente, commentato:
 
PHP version: 7.2.4
Sending to 
te...@icosaedro.it
:
bool(true)   {atteso}
NULL
Sending to <<<>>>:
bool(false)   {atteso}
NULL         {peccato che non si abbia il motivo del fallimento}
Sending to DoesNotExist@DoesNotExists:
bool(true)   {evidentemente il mio SMTP non fa una verifica dominio preventiva, ok}
NULL
Sending to 
te...@icosaedro.it
, <<<>>>:
bool(true)   {inaspettato, ma il primo destinatario riceve}
NULL
Sending to 
te...@icosaedro.it
, DoesNotExist@DoesNotExists:
bool(true)   {a questo punto è il risultato atteso}
NULL
Sending to <<<>>>, DoesNotExist@DoesNotExists:
bool(true)   {a questo punto, esito atteso}
NULL
You have new mail in /var/mail/test {sono arrivati i bounce!}
 
Sto cercando di irrobustire il metodo di trasporto mail() di PHPMailer,
ma senza successo.
 
Sono corrette le mie deduzioni?
 
C'è un altro modo di sapere perché mail() fallisce?
 
O bisogna proprio inviare un email alla volta?
 
Ciao,
 ___ 
/_|_\  Umberto Salsi
\/_\/  
www.icosaedro.it
 
 

 più destinatari. Sebbene il manuale non lo dica, sembra che:
 
Dipende tutto dal server mail sottostante.
 
mail() non fa altro che accodare una mail nel server SMTP che hai 
configurato. In Windows mi pare lo faccia via SMTP stesso, mentre in 
Linux chiama il binario di sendmail (o l'equivalente fornito da postfix. 
Non so come funzioni per exim).
 
Cosa succede poi dipende tutto da quello.
 
Se il binario/SMTP invocato torna un codice di errore che mail() 
riconosce, puoi avere un minimo di feedback, altrimenti è semplicemente 
"fire and forget".
 
L'unico modo di irrobustirlo è di non usare mail(), ma di implementare 
un client SMTP e gestire la comunicazione col server, cosa che mi pare 
Zend Mail e altre librerie fanno.
 
Bye.

 Zend Mail e altre librerie fanno.
 
Sto mettendo mano alla mia implementazione di PHPMailer, che supporta
diversi metodi di trasporto, incluso SMTP, sendmail/qmail. Il metodo
mail() sembra il meno raccomandabile. Per sendmail/qmail sto cercando
di catturare qualche feedback dai processi; si vedrà.


 1. Non si può avere retroazione del motivo del fallimento.
 
Aggiornamenti sulla mia indagine:
 
Sembra che qualcuno sia riuscito ad ottenere il motivo del
fallimento di mail() andando a guardare l'ultimo errore generato con
error_get_last()['message']), ma non è chiaro il contesto di questi
casi; a me risulta invece che non viene mai settato, e neppure mail()
genera E_WARNING per cui non c'è nessun errore da gestire.
 
Ho scoperto che in realtà qualche retroazione si può ottenere sfruttando
il parametro opzionale di mail() che consente di impostare opzioni
custom. L'effetto di queste opzioni dipende dallo specifico processo
"sendmail" usato. Per esempio, con Exim e Sendmail si può mettere "-oep
-oeq -oi" per cui mail() ha sempre successo anche se alcuni destinatari
vengono respinti e anche se il messaggio è formalmente sbagliato (cioè
non rispetta il formato RFC822); in tali casi:
 
- l'errore viene loggato da sendmail nello stderr e quindi va a finire
nel log degli errori del server;
 
- il messaggio non viene inviato a nessun destinatario, neanche quelli
validi;
 
- nessun bounce viene generato.
 
Ne segue che il programma non ha più retroazione del fallimento, però
rimane una traccia permanente nel log file del server e si riducono i
fastidiosi bounce.
 
Per avere retroazione dell'errore a livello del programma bisogna per
forza usare direttamente il processo sendmail e catturarne lo stderr con
la triade popen(), fwrite() e pclose(), una cosa abbastanza acrobatica
che sto cercando di far fare a PHPMailer.
 
L'ultima possibilità è ricorrere direttamente al protocollo SMTP
perché solo questo dà il massimo controllo su quello che accade e
permette di saper quali destinatari sono stati accettati e quali no,
e sapere se alla fine della fiera il messaggio è stato accettato per
il recapito oppure no; anche questo sto cercando di curarlo in PHPMailer.
 
Spero che possa tornare utile a qualcuno.
