javaemailsmtp

How do I code my own SMTP server using Java?


I have a client written in Java that I want to use to test out sending email, but instead of using an already existing SMTP like Google, I want to have my own local server to test out sending mock emails between two mock emails.

I've been trying to look all over the internet for good sources on how to code a simple SMTP server, but I've had zero luck.

I do have a basic server code that when I run it, I can connect my client to it, but at the moment it won't handle any email functionality.

TCPServer.java

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.*;
import java.net.*;

public class TCPServer{
    private ServerSocket server;

    /**
     * The TCPServer constructor initiate the socket
     * @param ipAddress
     * @param port
     * @throws Exception
     */
    public TCPServer(String ipAddress, int port) throws Exception {
        if (ipAddress != null && !ipAddress.isEmpty())
            this.server = new ServerSocket(port, 1, InetAddress.getByName(ipAddress));
        else
            this.server = new ServerSocket(0, 1, InetAddress.getLocalHost());
    }

    /**
     * The listen method listen to incoming client's datagrams and requests
     * @throws Exception
     */
    private void listen() throws Exception {
        // listen to incoming client's requests via the ServerSocket
        //add your code here
        String data = null;
        Socket client = this.server.accept();
        String clientAddress = client.getInetAddress().getHostAddress();
        System.out.println("\r\nNew client connection from " + clientAddress);

        // print received datagrams from client
        //add your code here
        BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
        while ( (data = in.readLine()) != null ) {
            System.out.println("\r\nMessage from " + clientAddress + ": " + data);
            client.sendUrgentData(1);
        }
    }

    public InetAddress getSocketAddress() {
        return this.server.getInetAddress();
    }

    public int getPort() {
        return this.server.getLocalPort();
    }


    public static void main(String[] args) throws Exception {
        // set the server address (IP) and port number
        //add your code here
        String serverIP = "192.168.1.235"; // local IP address
        int port = 8088;

        if (args.length > 0) {
            serverIP = args[0];
            port = Integer.parseInt(args[1]);
        }
        // call the constructor and pass the IP and port
        //add your code here
        TCPServer server = new TCPServer(serverIP, port);
        System.out.println("\r\nRunning Server: " +
                "Host=" + server.getSocketAddress().getHostAddress() +
                " Port=" + server.getPort());
        server.listen();
    }

}

What can i add to my existing server code to make it handle email for my Client. I'll also post my email client as well.

ClientTester.java


import java.io.*;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;

/**
 * This program demonstrates a TCP client
 * @author jl922223
 * @version 1.0
 * @since 2020-12-12
 */

public class ClientTester{
    private Socket tcpSocket;
    private InetAddress serverAddress;
    private int serverPort;
    private Scanner scanner;

    /**
     * @param serverAddress
     * @param serverPort
     * @throws Exception
     */
    private ClientTester(InetAddress serverAddress, int serverPort) throws Exception {
        this.serverAddress = serverAddress;
        this.serverPort = serverPort;

        //Initiate the connection with the server using Socket.
        //For this, creates a stream socket and connects it to the specified port number at the specified IP address.
        //add your code here
        this.tcpSocket = new Socket(this.serverAddress, this.serverPort);
        this.scanner = new Scanner(System.in);
    }

    /**
     * The start method connect to the server and datagrams
     * @throws IOException
     */
/*    private void start() throws IOException {
        String input;
        //create a new PrintWriter from an existing OutputStream (i.e., tcpSocket).
        //This convenience constructor creates the necessary intermediateOutputStreamWriter, which will convert characters into bytes using the default character encoding
        //You may add your code in a loop so that client can keep send datagrams to server
        //add your code here
        while (true) {
            System.out.print ("C:");
            input = scanner.nextLine();
            PrintWriter output = new PrintWriter(this.tcpSocket.getOutputStream(), true);
            output.println(input);
            output.flush();
        }
    }*/

    public static void main(String[] args) throws Exception {
        // set the server address (IP) and port number
        //add your code here
        //IP: 192.168.1.235
        //Port: 8088
        InetAddress serverIP = InetAddress.getByName("smtp.google.com"); // local IP address
        int port = 25;
        if (args.length > 0) {
            serverIP = InetAddress.getByName(args[0]);
            port = Integer.parseInt(args[1]);
        }

        // call the constructor and pass the IP and port
        //add your code here
        ClientTester client = new ClientTester(serverIP, port);

//        client.start();

        try{

            client = new ClientTester(serverIP, port);

            System.out.println("\r\n Connected to Server: " + client.tcpSocket.getInetAddress());

            BufferedReader stdin;
            stdin = new BufferedReader (new InputStreamReader (System.in));

            InputStream is = client.tcpSocket.getInputStream ();
            BufferedReader sockin;
            sockin = new BufferedReader (new InputStreamReader (is));

            OutputStream os = client.tcpSocket.getOutputStream();
            PrintWriter sockout;
            sockout = new PrintWriter (os, true);

            System.out.println ("S:" + sockin.readLine ());

            while (true){
                System.out.print ("C:");

                String cmd = stdin.readLine ();

                sockout.println (cmd);

                String reply = sockin.readLine ();

                System.out.println ("S:" + reply);
                if (cmd.toLowerCase ().startsWith ("data") &&
                        reply.substring (0, 3).equals ("354"))
                {
                    do
                    {
                        cmd = stdin.readLine ();

                        if (cmd != null && cmd.length () > 1 &&
                                cmd.charAt (0) == '.')
                            cmd = "."; // Must be no chars after . char.

                        sockout.println (cmd);

                        if (cmd.equals ("."))
                            break;
                    }
                    while (true);

                    // Read a reply string from the SMTP server program.

                    reply = sockin.readLine ();

                    // Display the first line of this reply string.

                    System.out.println ("S:" + reply);

                    continue;
                }

                // If the QUIT command was entered, quit.

                if (cmd.toLowerCase ().startsWith ("quit"))
                    break;
            }
        }
        catch (IOException e)
        {
            System.out.println (e.toString ());
        }
        finally
        {
            try
            {
                // Attempt to close the client socket.

                if (client != null)
                    client.tcpSocket.close();
            }
            catch (IOException e)
            {
            }
            }
    }
}

The good news is that the ClientTester works when I connect it to smtp.google.com, but I don't want to use Google's, I want to have my own basic email server in Java.


Solution

  • Okay, found this early-development standalone version. Use this INSTEAD of your code; does everything your code does an more. Single-threaded ServerSocket handling, so only one connection at a time.

    package jc.lib.io.net.email.smtp.test1;
    
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.File;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.net.SocketTimeoutException;
    import java.net.UnknownHostException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    import jc.lib.io.net.email.JcEMailBasics;
    
    public class Test_SMTP_Server {
    
    
    
        static public boolean DEBUG = true;
    
    
    
        public static void main(final String s[]) throws UnknownHostException, IOException {
            final Test_SMTP_Server server = new Test_SMTP_Server(JcEMailBasics.SMTP_PORTS);
            server.start();
    
            try {
                Thread.sleep(1 * 60 * 60 * 1000);
            } catch (final InterruptedException e) { /* */ }
        }
    
    
    
        /*
         * OBJECT
         */
    
        private final ServerSocket[]    mSockets;
        private volatile boolean        mStopRequested;
        private static boolean          mReceivingData;
    
    
    
        public Test_SMTP_Server(final int[] pPorts) throws IOException {
            mSockets = new ServerSocket[pPorts.length];
            for (int i = 0; i < pPorts.length; i++) {
                final int port = pPorts[i];
                try {
                    mSockets[i] = new ServerSocket(port);
                } catch (final java.net.BindException e) {
                    new java.net.BindException("When mountin port " + port + ": " + e.getMessage()).printStackTrace();
                }
                System.out.println("Created server socket on port " + port);
            }
        }
    
    
    
        public void start() {
            mStopRequested = false;
            for (final ServerSocket ss : mSockets) {
                if (ss == null) continue;
    
                final Thread t = new Thread(() -> handleServerSocket(ss), "handleServerSocket(" + ss.getLocalPort() + ")");
                t.setDaemon(true);
                t.start();
            }
        }
        private void handleServerSocket(final ServerSocket pSS) {
            final String name = "handleServerSocket(" + pSS.getLocalPort() + ")";
            while (!mStopRequested) {
                System.out.println(name + "\tListening for connection...");
                try (final Socket socket = pSS.accept();
                        final BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), JcEMailBasics.DEFAULT_CHARSET_SMTP_POP3));
                        final BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), JcEMailBasics.DEFAULT_CHARSET_SMTP_POP3));) {
                    System.out.println(name + "\tGot new Socket.");
                    handle(socket, in, out);
                    System.out.println(name + "\tClosing Socket.");
                } catch (final IOException e) {
                    System.err.println("In " + name + ":");
                    e.printStackTrace();
                }
                System.out.println(name + "\tComm Done.");
            }
        }
    
        public void stop() {
            mStopRequested = true;
            for (final ServerSocket ss : mSockets) {
                try {
                    ss.close();
                } catch (final Exception e) { /* */ }
            }
        }
    
    
    
        static private void handle(final Socket pSocket, final BufferedReader pBR, final BufferedWriter pBW) throws IOException {
            //      send("+OK POP3 server ready <" + Test_EMails.SERVICE_ADDRESS + ">", out);
            send("220 cbsoft.dev SMTP " + JcEMailBasics.NAME, pBW);
    
            final StringBuilder sb = new StringBuilder();
    
            mainLoop: while (!pSocket.isClosed()) {
                final String read = read(pBR);
                if (read == null) break;
    
                switch (read) {
                    case JcEMailBasics.COMMAND_DATA: {
                        send("354 End data with <CR><LF>.<CR><LF>", pBW);
                        mReceivingData = true;
                        break;
                    }
                    case JcEMailBasics.COMMAND_END_OF_DATA: {
                        send("250 OK", pBW);
                        mReceivingData = false;
                        break;
                    }
                    case JcEMailBasics.COMMAND_QUIT: {
                        send("221 " + JcEMailBasics.NAME + " signing off", pBW);
                        break mainLoop;
                    }
                    default: {
                        final String correctedRead = read.startsWith(".") ? read.substring(1) : read;
                        sb.append(correctedRead + "\n");
                        if (!mReceivingData) send("250 Ok", pBW);
                    }
                }
            }
    
            final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
            final File file = new File("mails/inc_" + sdf.format(new Date()) + ".email.txt");
            file.getParentFile().mkdirs();
            final String msg = sb.toString();
            try (FileOutputStream fos = new FileOutputStream(file)) {
                fos.write(msg.getBytes());
            }
            System.out.println("File saved as " + file.getCanonicalPath());
        }
    
        static private void send(final String pMessage, final BufferedWriter pBW) {
            try {
                pBW.write(pMessage + "\n");
                pBW.flush();
                if (DEBUG) System.out.println("SENT:\t" + pMessage);
            } catch (final Exception e) {
                e.printStackTrace();
            }
        }
    
        static private String read(final BufferedReader pBR) throws IOException {
            try {
                final String reply = pBR.readLine();
                if (DEBUG) System.out.println("RECV:\t" + reply);
                return reply;
    
            } catch (final SocketTimeoutException e) {
                System.err.println("SERVER TIMEOUT");
            }
            return null;
        }
    
    
    
    }
    

    the only additional file you will need (also included in my previous answer; edited a bit):

    package jc.lib.io.net.email;
    
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.IOException;
    
    
    public class JcEMailBasics {
    
    
    
        static public final int     SMTP_PORT_1 = 25;
        static public final int     SMTP_PORT_2 = 587;
        static public final int     SMTP_PORT_3 = 465;
        static public final int[]   SMTP_PORTS  = { SMTP_PORT_1, SMTP_PORT_2, SMTP_PORT_3 };
    
        static public final int     POP_PORT_1          = 110;
        static public final int     POP_PORT_SSL        = 995;
        static public final int     POP_PORT_KERBEROS   = 1109;
        static public final int[]   POP_PORTS           = { POP_PORT_1, POP_PORT_SSL, POP_PORT_KERBEROS };
    
        // netstat -aon | findstr '587'
    
    
    
        static public final String DEFAULT_CHARSET_SMTP_POP3 = "8859_1";
    
        static public final String  NAME                = "JC Oblivionat0r POP3 Server";
        static public final String  SERVICE_ADDRESS     = "oblivionat0r@cbsoft.dev";
        static public final String  CONNECTION_CLOSED   = "CONNECTION_CLOSED_dtnt495n3479r5zb3tr47c3b49c3";
        static public final String  COMMAND_QUIT        = "QUIT";
        static public final String  COMMAND_DATA        = "DATA";
        static public final String  COMMAND_END_OF_DATA = ".";
    
    
    
        static public void send(final BufferedWriter pBufferedWriter, final String pMessage) throws IOException {
            pBufferedWriter.write(pMessage + "\n");
            pBufferedWriter.flush();
            System.out.println("SENT:\t" + pMessage);
        }
        static public String sendExpect(final BufferedWriter pBufferedWriter, final String pMessage, final BufferedReader pBufferedReader, final String... pExpectedResponsePrefixes) throws IOException {
            send(pBufferedWriter, pMessage);
            final String read = read(pBufferedReader);
            for (final String erp : pExpectedResponsePrefixes) {
                if (read.startsWith(erp)) return read;
            }
            throw new IllegalStateException("Bad response: Expected [" + toString(", ", pExpectedResponsePrefixes) + "] got [" + read + "] instead!");
        }
    
        static public String read(final BufferedReader pBufferedReader) throws IOException {
            final String reply = pBufferedReader.readLine();
            System.out.println("RECV:\t" + reply);
            return reply;
        }
    
        @SafeVarargs public static <T> String toString(final String pSeparator, final T... pObjects) {
            if (pObjects == null) return null;
            final StringBuilder ret = new StringBuilder();
            for (final T o : pObjects) {
                ret.append(o + pSeparator);
            }
            if (ret.length() > 0) ret.setLength(ret.length() - pSeparator.length());
            return ret.toString();
        }
    
    
    
    }