11

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?

informatik01
  • 16,038
  • 10
  • 74
  • 104
Reimius
  • 5,694
  • 5
  • 24
  • 42

2 Answers2

11

Try using ByteArrayDataSource, like this

ByteArrayOutputStream baos = //Read the output stream
DataSource aAttachment = new  ByteArrayDataSource(baos.toByteArray(),"application/octet-stream");
MimeBodyPart attachmentPart = new MimeBodyPart();

attachmentPart.setDataHandler(new DataHandler(aAttachment));
NullPointerException
  • 3,732
  • 5
  • 28
  • 62
  • I found this solution right after I asked the question... but this solution needs to hold the whole object in memory. I would prefer a solution that avoids this, but I will mark your answer as correct if nothing else comes up. – Reimius Apr 01 '13 at 13:34
  • 2
    So where else you want to put your object. You have to store the object somewhere either in memory or file system. – NullPointerException Apr 01 '13 at 13:57
  • 3
    The point of a stream is that the object is streamed through from the source to destination and only read a bit at a time, it never completely resides in memory. 'baos.toByteArray()' just stuffs the whole thing right in memory wherever that array is as bytes. I'm not reading the stream from the system doing the processing, so it should never have to store the object in a complete form. – Reimius Apr 01 '13 at 14:25
  • 2
    Unless you're trying to say that the java email classes would internally do this anyway and the streaming is thus redundant. If you can point my way to documentation proving this, I would agree. – Reimius Apr 01 '13 at 14:27
  • 1
    Please see the answer here: https://stackoverflow.com/questions/15709823/how-to-add-attachments-to-email-in-java-using-outputstream/48493521#48493521 for how to stream data to java mail. I believe this should be marked as the correct answer. – Scott Aug 21 '18 at 20:34
4

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.

Scott
  • 486
  • 4
  • 14
  • The sample code above is for demonstration purposes. It may be more practical (from an object member access standpoint) in production code to reverse the function of each thread, i.e., the subordinate thread runs JavaMail and the parent thread writes to the output stream. – Scott May 05 '18 at 18:20
  • My co-worker looked through this and said it was legit. I don't have a need for this anymore, but I'll give you the points. – Reimius Jun 14 '19 at 19:02
  • 1
    Thanks, much appreciated. It really is the proper solution. I've implemented it (although with reverse sense of the source/sink threads as indicated in my previous comment) in a J2EE server (both Tomcat and Open Liberty) for mailing rather large PDF attachments with minimal memory footprint (64KB buffer per HTTPSession). – Scott Jun 15 '19 at 04:12