Come richiesto, avvio questa nuova pillola.
L'argomento, come indicato nel titolo, è l'invio delle mail utilizzando Java.
La classe che posterò sarà in grado di inviare una mail a più destinatari (anche in copia) e di aggiungere un (uno solo!) eventuale allegato.

Premessa: l'invio delle mail avviene tramite connessione con un server SMTP. Questo significa che tutto il traffico sarà gestito dal protocollo SMTP (Rif: RFC 821 e RFC 2821).

Vediamo come funzionerà la classe: la classe contiene un solo metodo pubblico statico che prende in ingresso i seguenti parametri:

01) Nome o indirizzo IP del server SMTP
02) Elenco di destinatari
03) Elenco di eventuali destinatari in copia
04) Oggetto della mail
05) Messaggio (come elenco di righe)
06) Nome dell'eventuale file allegato
07) Nome utente per eventuale autenticazione
08) Password per eventuale autenticazione
09) Nome da visualizzare nel campo "From"
10) Indirizzo da utilizzare nel campo "From"
11) Nome da utilizzare per il file allegato
12) Indirizzo a cui inviare l'eventuale conferma di lettura
13) Un parametro che indica se è necessaria l'autenticazione
14) Un parametro che indica se è richiesta la conferma di recapito

e li utilizza, ovviamente, per fare le sue considerazioni.
La classe, per poter effettuare l'autenticazione, necessita di una seconda classe da richiamare per ottenere le stringhe di accesso codificate, se l'accesso prevede autenticazione CRAM-MD5 (Ref: RFC 2195 e RFC 1321).
Non sto a spiegare nel dettaglio come avviene la comunicazione SMTP fra la classe ed il server (a tale scopo, per chi fosse interessato, ho aggiunto il riferimento alle RFC).
A grandi linee avviene questo: la classe effettua una connessione sulla porta 25 con il server; questo si presenta ed inizia il dialogo. La classe si presenta, il server risponde indicando tutte le feature che mette a disposizione, quindi la classe invia la richiesta per una nuova mail. Invia tutta la mail utilizzando lo standard MIME (RFC 1521 e RFC 2045), quindi chiude la connessione con il server.

Il codice della classe principale è il seguente:
codice:
import java.io.*;
import java.net.*;

public class SendMail {
   static  int SMTPport =  25;
   static  Socket  socket;
   static  boolean[] authMechanism = null;

   public static void sendMail(String mailServer,
                               String[] recipient,
                               String[] cc,
                               String subject,
                               String[] messaggio,
                               String fileName,
                               String userName,
                               String password,
                               String nomeUtente,
                               String from,
                               String nomeFileVis,
                               String confLettura,
                               boolean requireLogin,
                               boolean confReceipt) {
      String linea = "";
      boolean autenticated = false;
      authMechanism = new boolean[3];
      authMechanism[0] = false;   // CRAM-MD5
      authMechanism[1] = false;   // LOGIN
      authMechanism[2] = false;   // PLAIN

      try {
         Socket s = new Socket(mailServer, 25);

         BufferedReader in = new BufferedReader( new InputStreamReader(s.getInputStream(), "8859_1") );
         BufferedWriter out = new BufferedWriter( new OutputStreamWriter(s.getOutputStream(), "8859_1") );

         linea = in.readLine();

         String boundary = "Dat_Sep_Str_#COD#";   // -- Data Separator String --
         sendln(in, out, "EHLO " + InetAddress.getLocalHost().getHostName());   // + userName

         recuperaMechanism( in );

         if ( requireLogin ) {
            for(int i=0; i<authMechanism.length; i++) {
               if ( authMechanism[i] && !autenticated ) {
                  autenticated = true;
                  sendLogin(0, in, out, userName, password, i);
               }
            }
            if ( !autenticated ) {
               System.out.println("Autenticazione non riuscita: nessun metodo di autenticazione rilevato o metodo non supportato!");
            }
         }

         sendln(in, out, "MAIL FROM: <"+ InetAddress.getLocalHost().getHostName() + ">");
         for (int i=0; i<recipient.length; i++)
            sendln(in, out, "RCPT TO: <" + recipient[i] + ">" + (confReceipt ? " NOTIFY=SUCCESS" : "") );
         for (int i=0; i<cc.length; i++)
            sendln(in, out, "RCPT TO: <" + cc[i] + ">");
         sendln(in, out, "DATA");
         sendln(out, "MIME-Version: 1.0");
         sendln(out, "Subject: " + subject);
         sendln(out, "From: " + userName + " <" + from + ">");
         for (int i=0; i<recipient.length; i++)
            sendln(out, "To: <" + recipient[i] + ">");
         for (int i=0; i<cc.length; i++)
            sendln(out, "Cc: <" + cc[i] + ">");
         sendln(out, "Disposition-Notification-To: "+confLettura);
         sendln(out, "X-Confirm-Reading-To: "+confLettura);
         sendln(out, "Content-Type: multipart/mixed; boundary=\"" + boundary +"\"");
         sendln(out, "\r\n--" + boundary);

         // Send the body
         sendln(out, "Content-Type: text/plain; charset=\"us-ascii\"\r\n");
         for (int i=0; i<messaggio.length; i++)
            sendln(out, messaggio[i]);
         sendln(out, "\r\n--" +  boundary );

         // send the attachment
         String nomeFile = (new File(nomeFileVis)).getName();
         sendln(out, "Content-Type:Application/Octet-stream; name="+nomeFile);
         sendln(out, "Content-Disposition: attachment;filename=\""+nomeFile+"\"");
         sendln(out, "Content-transfer-encoding: base64\r\n");
         MIMEBase64.encode(fileName, out);

         sendln(out, "\r\n\r\n--" + boundary + "--\r\n");
         sendln(in, out,".");
         sendln(in, out, "QUIT");
         s.close();
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   private static void recuperaMechanism(BufferedReader in) throws Exception {
      String linea = "";
      boolean termina = false;

      while( !termina ) {
         linea = in.readLine();
         termina = linea.charAt(3) != '-';
         authMechanism[2] = authMechanism[2] || ((linea.indexOf("AUTH") > 0) && (linea.indexOf("LOGIN") > 0));
         authMechanism[1] = authMechanism[1] || ((linea.indexOf("AUTH") > 0) && (linea.indexOf("PLAIN") > 0));
         authMechanism[0] = authMechanism[0] || ((linea.indexOf("AUTH") > 0) && (linea.indexOf("CRAM-MD5") > 0));
      }
   }

   public static void sendLogin(int tipo, BufferedReader in, BufferedWriter out, String utente, String password, int metodo) {
      String resp = "";
      try {
         switch( metodo ) {
            case 0:   // AUTH CRAM-MD5

               out.write("AUTH CRAM-MD5" + "\r\n");
               out.flush();
               resp = in.readLine();

               if ( !resp.startsWith("334") ) {
                  System.out.println("Errore: il server non ha risposto in modo atteso alla richiesta di AUTH CRAM-MD5: " + resp);
               } else {
                  resp = resp.substring(4, resp.length());
                  resp = CRAM_MD5.getLoginString(utente, password, resp);
                  out.write(resp + "\r\n");
                  out.flush();
                  resp = in.readLine();

                  if ( !resp.startsWith("235") ) System.out.println("Errore: il server non ha accettato l'autenticazione: " + resp);
               }
               break;

            case 1:   // AUTH LOGIN

               out.write("AUTH LOGIN" + "\r\n");
               out.flush();
               resp = in.readLine();

               if ( !resp.equals("334 VXNlcm5hbWU6") ) System.out.println("Errore: il server non ha risposto in modo atteso alla richiesta di AUTH LOGIN: " + resp);

               out.write((new sun.misc.BASE64Encoder()).encode(utente.getBytes("UTF8")) + "\r\n");
               out.flush();
               resp = in.readLine();

               if ( !resp.equals("334 UGFzc3dvcmQ6") ) System.out.println("Errore: il server non ha accettato il nome utente: " + resp);

               if ( !password.equals("") ) {
                  out.write((new sun.misc.BASE64Encoder()).encode(password.getBytes("UTF8")) + "\r\n");
                  out.flush();
                  resp = in.readLine();

                  if ( !resp.startsWith("235") ) System.out.println("Errore: il server non ha accettato il nome utente: " + resp);
               }
               break;

            case 2:   // AUTH PLAIN

               out.write("AUTH PLAIN" + "\r\n");
               out.flush();
               resp = in.readLine();

               if ( !resp.startsWith("334") ) System.out.println("Errore: il server non ha risposto in modo atteso alla richiesta di AUTH PLAIN: " + resp);

               resp = "\0" + utente + "\0" + password;
               out.write((new sun.misc.BASE64Encoder()).encode(resp.getBytes("UTF8")) + "\r\n");
               out.flush();
               resp = in.readLine();

               if ( !resp.startsWith("235") ) System.out.println("Errore: il server non ha accettato l'autenticazione: " + resp);
               break;
         }

      } catch (Exception e) { e.printStackTrace(); }
      
   }

   private static void sendln(BufferedReader in, BufferedWriter out, String s) {
      try {
         out.write(s + "\r\n");
         out.flush();
         s = in.readLine();
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   private static void sendln(BufferedWriter out, String s) {
      try {
         out.write(s + "\r\n");
         out.flush();
      } catch (Exception e) {
         e.printStackTrace();
      }
   }
}