The blogging platform that is running on this site offers a multitude of features, including the ability for readers to subscribe. A problem is, without relying on 3rd party services you really can't send emails to your subscribers notifying them of new posts. I created a Java program to do just that.

Conditions

I have acquired a GSuite account and connected it to my domain so that email sent to armando.herrera@themadphysicist.com was hosted by google. This custom domain google account is able to access through IMAPS for in going emails and SMTPS for outgoing emails. Furthermore, the blogging platform, that uses nodejs as a backbone, as the ability to export these subscriber's email to a downloadable CSV file.

So, the goal for the Java program is to import the subscriber information from the CSV file, connect to the google email server via TLS encrypted SMTPS, and mass send a html emails notifying the subscribers that there is a new post.

Details

First and foremost, I used one of my favorite IDEs for this project, Jet Brain's IntelliJ IDEA which has built in support for the Gradle package manager, which we will use. The IDE has a template for gradle project which we will use.

In the template, we will modify a config file so that gradle automatically install the necessary dependencies. After creating a project called EmailNotifier, in the file build.gradle make sure that the stuff between the dependencies brackets looks like this.

dependencies {
  compile group: 'org.json', name: 'json', version: '20180813'
  compile group: 'com.sun.mail', name: 'javax.mail', version: '1.6.5'
  compile group: 'org.apache.commons', name: 'commons-csv', version: '1.6'
}

The first entry will import the package that will parse JSON format text, the second entry will import the enterprise version of the JavaMail package which will take care of sending our emails, and, our last entry, will import the package that will parse the CSV format text.

Sending Email

Eduonix created a great tutorial for how to use the Java Mail package, link, but, I will go over how to implement for my particular situation.

I will be solely logging into google's SMTP server using TLS. The package mainly uses 3 objects, java.util.Properties, javax.mail.Session, and javax.mail.internet.MimeMessage. The first of the three will contains some of the configurations used, like the port, whether you need authorization, whether to use SSL, TLS, or STARTTLS, of which we will use STARTTLS. you can find more information about these here. The second will hold information like where the email is going to, the subject of the email, and the contents of the email, which are stored in the MimeMessage Object.

Let's create a class and a static function that will send emails. As input parameters, let's have 7 string each will pass different info to our function.

// EmailEngine.java
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.Properties;

class EmailEngine {
  static void generateAndSendEmail(String host, String port, String user, String password,
                                          String subject, String mailContents, String to)
                                          throws MessagingException {
    Properties mailServerProperties;
    Session getMailSession;
    MimeMessage generateMailMessage;
    
    // Setup Server Properties
    mailServerProperties = System.getProperties();
    mailServerProperties.put("mail.smtp.port", port);
    mailServerProperties.put("mail.smtp.auth", "true");
    mailServerProperties.put("mail.smtp.starttls.enable", "true");
    
    // Setup mail session
    getMailSession = Session.getDefaultInstance(mailServerProperties, null);
    generateMailMessage.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
    generateMailMessage.setSubject(subject);
    generateMailMessgae.setContent(mailContents, "text/html");
    
    // Send Email
    Transport transport = getMailSession.getTransport("smtp");
    
    // email and password goes here
    transport.connect(host, user, password);
    transport.sendMessage(generateMailMessage, generateMailMessage.getAllRecipients());
    transport.close();
  }
}

Line 1-7 are our imports, lines 13-15 are the three objects we will need, on lines 17-21 we set up our server settings, lines 23-27 are dedicated to the settings and the content of our email, line 30 sets up a transport session that sends our email, and, finally, lines 33-35 sends our email.

Getting Sub Info

The CSV file that is generated by the platform's format is that of a standard CSV file with the first column containing a unique id for each subscriber, the second column contains their email, the 4th contains when they first subscribed, and the last contains when and whether they deleted their subscription. So, we need a class that will encapsulate that information.

// Subscriber.java
public class Subscriber {
  private String id, email, datetimeSubbed, deleted_at;
  public Subscriber() {}
  public Subscriber(String id, String email, String datetimeSubbed, String deleted_at) {
  this.id = id;
  this.email = email;
  this.datetimeSubbed = datetimeSubbed;
  this.deleted_at = deleted_at;
  }
  public String getId() {
    return id;
  }
  public String getEmail() {
    return email;
  }
  public String getDatetimeSubbed() {
    return datetimeSubbed;
  }
  public String getDeleted_at() {
    return deleted_at;
  }
  public void setId(String id) {
    this.id = id;
  }
  public void setEmail(String email) {
    this.email = email;
  }
  public void setDatetimeSubbed(String datetimeSubbed) {
    this.datetimeSubbed = datetimeSubbed;
  }
  public void setDeleted_at(String deleted_at) {
    this.deleted_at = deleted_at;
  }
}

Now, why would I want to get all that information when I'm only going to use their information? Well, I might integrate that information in the future, so it is best to have it from the beginning.

To get the data from the CSV file to memory, we are going to use the org.apache.commons.csv package. We need to import three objects from that package, org.apache.commons.csv.CSVFormat, org.apache.commons.csv.CSVParser, and org.apache.commons.csv.CSVRecord, the first will contain the format to read the CSV file with, the second will do the parsing, and the third will contain some of the info.

// CSVReader.java
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;

import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

class CSVReader {
  static List<Subscriber> parseCSV(String path) throws IOException {
    List<Subscriber> subs = new ArrayList<>();
    Reader reader = Files.newBufferedReader(Paths.get(path));
    CSVParser csvParser = new CSVParser(reader, CSVFormat.DEFAULT.withFirstRecordAsHeader()
        .withIgnoreHeaderCase9).withTrim());
    for (CSVRecord csvRecord : csvParser) {
      subs.add(new Subscriber(csvRecord.get("id"), csvRecord.get("email"),
          csvRecord.get("created_at"), csvRecord.get("deleted_at")));
    }
    return subs;
  }
}

In the function, I stored the subscriber information in the List<Subscriber> container initiated as a ArrayList. the CSVParser object has a java.io.Reader object as an input so that method getting the text is used, at lines 16-18. CSVParser uses the reader object to read the text and parses it. The data is then stored within the object. We can access each column using a for each loop and add that to the list.

Storing settings

We do need to be able to store the settings that sending the emails require, including stuff like the host name, the port number, the username, the password, te path to CSV file, the subject for the email, and the path for the html file to embed in the email. Doing this is simple using JSON as the format,

{
    "host":"smtp.gmail.com",
    "port":"587",
    "user":"you email goes here",
    "password":"you password goes here",
    "subcsvpath":"path to csv",
    "emailsubject":"The Mad Physicist: New Post Notification",
    "emailhtmlpath":"path to html"
}

To transfer the setting into our program we will use the org.json package and store the settings in the class for later use. I used the constructor to do the parsing.

// JSONReader.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import org.json.JSONException;
import org.json.JSONObject;

class JSONReader {
  String host;
  String port;
  String password;
  String subcsvpath;
  String subject;
  String htmlpath;
  JSONReader(Path configpath) throws JSONException, IOException {
    String jsontxt = new String(Files.readAllBytes(configpath));
    JSONObject json = new JSONObject(jsontxt);
    host = json.getString("host");
    port = json.getString("port");
    user = json.getString("user");
    password = json.getString("password");
    subcsvpath = json.getString("subcsvpath");
    subject = json.getString("emailsubject");
    htmlpath = json.getString("emailhtmlpath");
  }
}

Line 17 reads from the text file and stores the text in the string called jsontxt and in line 18 the org.json package parses it. We then access the data by calling the variable names as they appear in the JSON text, lines 19-25.

Putting it all together

When creating command line programs or GUI programs I like to create a separate class explicitly just for the public static void main function. In this program I called that class EntryClass. Firstly, the function uses the JSONReader class to get and parse the JSON config file called config.json. Then using the path of the CSV obtained, it uses the static function parseCSV in the class CSVReader to get the subscriber information. We get the read the contents of the html file and store it in a string. Finally, we iterate through the subscribers sending emails to each.

// EntryClass.java
import javax.mail.MessagingException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

public class EntryClass {
  public static void main(String[] args) {
    String jcp = "config.json";
    try {
      JSONReader jsonreader = new JSONReader(Paths.get(jcp));
      List<Subscriber> subscribers = CSVReader.parseCSV(jsonReader.subcsvpath);
      String emailContents = new String(Files.readAllBytes(Paths.get(jsonReader.htmlpath)));
      for (Subscriber subscriber : subscribers) {
        System.out.println("Sending new post email to: " + subscriber.getEmail());
        EmailEngine.generateAndSendEmail(jsonReader.host, jsonReader.port, jsonReader.user, jsonReader.password,
            jsonReader.subject, emailContents, subscriber.getEmail());
      }
    } catch (IOException | MessagingException ex) {
      ex.printStackTrace(System.err);
    }
  }
}

Finishing touches

The only thing that remains is to create an html page notifying the subs of a new post. I went with a very simplistic design that I saw the sent out from the ghost website.

SOURCE CODE

Edit: Here is a more comprehensive guide from folks over at mailtrap, link.