javaemailfile-iooutputstream

How to add attachments to email in Java using OutputStream?


I've seen the code for javax.mail library where you add attachments to the email doing this:

MimeBodyPart attachmentPart = new MimeBodyPart();
FileDataSource fds = new FileDataSource("C:/text.txt");
attachmentPart.setDataHandler(new DataHandler(fds));
attachmentPart.setFileName("text.txt");
multipart.addBodyPart(attachmentPart);

But this requires that the file reside somewhere on this disk.

I would like to grab an OutputStream right from the email library and stream file contents into it directly from another place where I write to that OutputStream.

Is this possible?


Solution

  • Yes, this is possible. The answer employing ByteArrayDataSource does not provide a satisfactory solution for large attachments because it requires that the entire content reside in memory at once. A better solution is to use a DataHandler that is fed by a PipedInputStream, which in turn is written to by a PipedOutputStream. Of course, this requires a second Thread. The code below demonstrates this:

    import com.sun.mail.smtp.*;
    import java.io.InputStream;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.io.OutputStream;
    import java.io.OutputStreamWriter;
    import java.io.PipedInputStream;
    import java.io.PipedOutputStream;
    import java.util.Date;
    import java.util.Properties;
    import javax.activation.DataHandler;
    import javax.activation.DataSource;
    import javax.mail.*;
    import javax.mail.internet.*;
    
    public class javamail {
    
        // Piped Data Source
    
        private static class PipedDataSource  implements DataSource {
            InputStream in;
            String type;
            public PipedDataSource (InputStream in, String type) { this.in = in; this.type = type; }
            public String getContentType() { return type; }
            public InputStream getInputStream() { return in; }
            public String getName() { return "DataSource"; }
            public OutputStream getOutputStream() throws IOException { throw new IOException("No OutputStream"); }
        }
    
        // Main Method
    
        public static void main(String [] args) throws Exception {
    
            final int BUFFER_SIZE = 32768;
    
            Properties properties = new Properties();
            properties.put("mail.smtp.starttls.enable", System.getProperty("mail.smtp.starttls.enable","true"));
            properties.put("mail.smtp.ssl.trust", System.getProperty("mail.smtp.ssl.trust","*"));
            properties.put("mail.smtp.host", System.getProperty("mail.smtp.host","localhost"));
            properties.put("mail.smtp.port", System.getProperty("mail.smtp.port","587"));
    
            Session session = Session.getInstance(properties);
    
            String host = properties.getProperty("mail.smtp.host");
            int port = Integer.parseInt(properties.getProperty("mail.smtp.port"));
            System.err.println("connect: smtp://"+host+":"+port); System.err.flush();
    
            MimeMessage msg = new MimeMessage(session);
    
            PipedInputStream in = new PipedInputStream(BUFFER_SIZE);
            PipedOutputStream out = new PipedOutputStream(in);
    
            // Set general headers
    
            msg.setFrom(System.getProperty("mail.from","Unknown <unknown@example.com>"));
            msg.setRecipients(Message.RecipientType.TO, System.getProperty("mail.to", "unknown@example.com"));
            msg.setSentDate(new Date());
            msg.setSubject("JavaMail Test");
            msg.setHeader("X-Mailer", "JavaMail");
    
            // Set main text - Part 1 - content provided here
            MimeBodyPart part1 = new MimeBodyPart();
            StringBuilder sb = new StringBuilder();
            sb.append("This is the cover letter that describes the accompanying  \n");
            sb.append("attachment, which is a base64 encoded text document of  \n");
            sb.append("little more value than a demonstration.\n\n");
            part1.setText(sb.toString()); // Writes a computed Content-Type Header
            part1.setHeader("Content-Type","text/plain; charset=us-ascii; format=flowed; delsp=yes"); // Rewrite Header
    
            // Set attachment - Part 2 - content provdied from another thread via a pipe
            MimeBodyPart part2 = new MimeBodyPart();
            part2.setDataHandler(new DataHandler(new PipedDataSource (in, "text/html"))); // Writes a Content-Type Header
            part2.setHeader("Content-Type","text/plain; charset=\"utf-8\"; name=\"Lorem Ipsum.txt\""); // Rewrite Header
            part2.setHeader("Content-Disposition", "attachment; filename=\"Lorem Ipsum.txt\"");
            part2.setHeader("Content-Transfer-Encoding","base64");
    
            // Join parts
            MimeMultipart multipart = new MimeMultipart();
            multipart.addBodyPart(part1);
            multipart.addBodyPart(part2);
            msg.setContent(multipart);
    
            // Start thread to deliver content for Part 2 attachment via DataHandler
            Thread t = new Thread() {
                public void run() {
                    try {
                        PrintWriter w = new PrintWriter(new OutputStreamWriter(out,"UTF-8"));
                        w.print("Lorem ipsum dolor sit amet, ligula suspendisse nulla pretium");
                        w.print(", rhoncus tempor fermentum, enim integer ad vestibulum volut");
                        w.print("pat. Nisl rhoncus turpis est, vel elit, congue wisi enim nun");
                        w.print("c ultricies sit, magna tincidunt. Maecenas aliquam maecenas ");
                        w.print("ligula nostra, accumsan taciti. Sociis mauris in integer, a ");
                        w.print("dolor netus non dui aliquet, sagittis felis sodales, dolor s");
                        w.print("ociis mauris, vel eu libero cras. Faucibus at. Arcu habitass");
                        w.print("e elementum est, ipsum purus pede porttitor class, ut adipis");
                        w.print("cing, aliquet sed auctor, imperdiet arcu per diam dapibus li");
                        w.print("bero duis. Enim eros in vel, volutpat nec pellentesque leo, ");
                        w.print("temporibus scelerisque nec.");
                        w.println("");
                        w.println("");
                        w.flush(); // Ensure data completely flushed to buffer
                        w.close(); // closes the writer and PipedOutputStream
                    } catch(Exception e) { e.printStackTrace(); };
                    try { out.close(); } catch(Exception e) { e.printStackTrace(); }
                }
            };
            t.start();
    
            // Send the message on its way
            SMTPTransport xp = (SMTPTransport) session.getTransport();
            xp.connect();
            xp.sendMessage(msg,msg.getAllRecipients());
            System.err.println(xp.getLastServerResponse());
    
            t.join();
            return;
        }
    }
    

    You can run this code with the following properties (edited as appropriate) defined on the command line:

    -Dmail.from="sender@host.example.com"
    -Dmail.to="receipient@host.example.com"
    -Dmail.smtp.host=smtp.example.com
    -Dmail.smtp.port=587 or 25
    

    The example code sends an email with a text/plain cover letter with us-ascii encoding and a text/plain attachment with utf-8 encoding with a base64 transfer encoding. It also uses STARTTLS (encrypted transfer) if the MX supports it.

    Update: Starting with Java9 the javax.activation.* classes have moved. Look here for the solution.