Build MSG File Analyzer using NextJS

Build MSG File Analyzer using NextJS

When applying for a tech job it is important to have a strong portfolio to impress the company. Especially for beginner developers the first impression of resume and projects are playing crucial role. The most common project types are To-do app, Weather app, Blog and etc. which is boring.

This post cross-published from Notion with OnePublish.

In this new series we will build unique and creative stuff and ship it to production. Check out previous publication from this series below:

https://www.developertimeline.io/build-a-phishing-site-detector-with-nextjs-and-virustotal/

So, in this episode we will build Email Detail Viewer that will take the MSG file as input, read its content and display it to the user.

We’re going to use a NextJS to build our app fast and ship it to production easily using Vercel. Each time we make a new commit Vercel will update our app so we don’t need to worry about deployment but only focus on building.

Why this project is great?

Because it involves many skills those required to have as a developer:

  • TypeScript.
  • Third-party integration.
  • Use of NextJS itself.
  • UI/UX skills.
  • Tailwind CSS (eh)
  • Deployment

What we are building?

This project is going to be a single page app that will let user to upload MSG File or in other terms Microsoft Outlook Item. After user uploads file, it should extract the email headers, content, attachments and other details of email then display the result to the user with nice UI/UX.

Getting Started

Let’s start by creating a new NextJS project. I will be using latest version of NextJS which is version 14 at time of writing this post.

npx create-next-app@latest

It will prompt few questions to configure the NextJS project. I will select to use TypeScript, initialise the Tailwind CSS and use App router.

✔ What is your project named? … <your_app_name_here>
✔ Would you like to use TypeScript? … No / **Yes**
✔ Would you like to use ESLint? … No / **Yes**
✔ Would you like to use Tailwind CSS? … No / **Yes**
✔ Would you like to use `src/` directory? … **No** / Yes
✔ Would you like to use App Router? (recommended) … No / **Yes**
✔ Would you like to customize the default import alias (@/*)? … No / **Yes**

Now you should have initialised NextJS project ready to use.

Building Backend

First, we need to find a library that can read the content of msg file. There is a npm package named @kenjiuno/msgreader which can read and extract the details from the .msg files.

Let's start by creating and endpoint called /api/reader that will handle POST request from frontend and pass file data to the msg reader.

/api/reader/route.ts

import { reader } from "@/app/utils/reader";

export async function POST(request: Request) {
  const formData = await request.formData();

  const file: any = formData.get("file");
  const buffer = Buffer.from(await file.arrayBuffer());

  const readerRes = await reader(buffer);
  return Response.json({ reader: readerRes });
}

Simply, getting and reading the file from request body and storing the buffer inside buffer variable.

There is no need to save uploaded file locally since reader accepts Buffer as well. That saves us from a lot of file IO operations.

Now, let's add a reader util function:

/utils/reader.ts

import MsgReader from "@kenjiuno/msgreader";

export async function reader(buffer: Buffer) {
  const testMsg = new MsgReader(buffer);
  const testMsgInfo = testMsg.getFileData();
  return testMsgInfo;
}

We're using @kenjiuno/msgreader library to extract the details from file buffer.

That's pretty all from backend side!

Building Frontend

On the frontend side, we're going to use DaisyUI and some Flowbite components to quickly build UI. Both built on top of Tailwind CSS.

Here's the index page that holds all components for this app:

app/page.tsx

"use client";

import { useState } from "react";
import FileInput from "./components/FileInput";
import EmailHeaders from "./components/Headers";
import AttachmentsHeaders from "./components/Attachments";
import { FieldsData } from "@kenjiuno/msgreader";

const Home: React.FC = () => {
  const [details, setDetails] = useState<FieldsData | null>(null);

  return (
    <>
      <FileInput onDataExtracted={(details: FieldsData) => setDetails(details)} />
      {/* Results */}
      <EmailHeaders headerDetails={details?.headers} />
      <AttachmentsHeaders attachmentsDetails={details?.attachments} />
    </>
  );
};

export default Home;

The first component will be FileInput that will allow user to upload their MSG file to the drag & drop input area.

/components/FileInput.tsx

"use client";

import { FieldsData } from "@kenjiuno/msgreader";
import axios from "axios";
import { ChangeEvent, useState } from "react";

interface FileInputProps {
  onDataExtracted: (details: FieldsData) => void
}

const FileInput: React.FC<FileInputProps> = ({ onDataExtracted }) => {
  const [alertMessage, setAlertMessage] = useState<string>("");

  const submitMsgFile = async (event: ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (file) {
      try {
        const formData = new FormData();
        formData.append("file", file);
        const res = await axios.post("/api/reader", formData);
        onDataExtracted(res?.data?.reader);
        console.log(res.data.reader);
        setAlertMessage("Success! Check results below.");
      } catch (error) {
        console.log(error);
      }
    } else {
    }
  };
  return (
    <div className="hero min-h-screen bg-base-200">
      <div className="hero-content text-center">
        <div className="max-w-lg">
          <h1 className="text-5xl font-bold mb-5">MSG Analyzer</h1>
          <p className="mb-5">Drop your .MSG file below to get results</p>

          <div className="flex items-center justify-center w-full">
            <label className="flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800 dark:bg-gray-700 hover:bg-gray-100 dark:border-gray-600 dark:hover:border-gray-500 dark:hover:bg-gray-600">
              <div className="flex flex-col items-center justify-center pt-5 pb-6">
                <svg
                  className="w-8 h-8 mb-4 text-gray-500 dark:text-gray-400"
                  aria-hidden="true"
                  xmlns="http://www.w3.org/2000/svg"
                  fill="none"
                  viewBox="0 0 20 16"
                >
                  <path
                    stroke="currentColor"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                    strokeWidth="2"
                    d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"
                  />
                </svg>
                <p className="mb-2 text-sm text-gray-500 dark:text-gray-400">
                  <span className="font-semibold">Click to upload</span> or drag
                  and drop
                </p>
                <p className="text-xs text-gray-500 dark:text-gray-400">
                  SVG, PNG, JPG or GIF (MAX. 800x400px)
                </p>
              </div>
              <input
                id="dropzone-file"
                type="file"
                className="hidden"
                onChange={submitMsgFile}
              />
            </label>
          </div>

          {alertMessage && (
            <div role="alert" className="alert">
              <svg
                xmlns="http://www.w3.org/2000/svg"
                className="stroke-current shrink-0 h-6 w-6"
                fill="none"
                viewBox="0 0 24 24"
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  strokeWidth="2"
                  d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
                />
              </svg>
              <span>{alertMessage}</span>
              <div>
                <a href="#results" className="btn btn-sm btn-primary">
                  Get Results
                </a>
              </div>
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

export default FileInput;

The submitMsgFile handler function listens onChange event from input field. That mean's once user uploads file it wil trigger the handler function.

Next, passing the file to the FormData and sending to the endpoint.

As you noticed this component has callback prop which sends result of reader to the parent component. In page level, we will pass the result to other components to display the result.

Let's now move to the Headers component that displays result of email headers:

components/Headers.tsx

"use client";

interface EmailHeadersProps {
  headerDetails: string | undefined;
}

const EmailHeaders: React.FC<EmailHeadersProps> = ({ headerDetails }) => {
  return (
    <div className="px-16 my-8" id="results">
      {headerDetails && (
        <>
          <h3 className="text-2xl font-bold mb-2">Email Headers</h3>
          <div className="mockup-code">
            <pre data-prefix="~" className="text-xs text-sky-500">
              <code
                style={{
                  display: "block",
                  marginLeft: "50px",
                  whiteSpace: "pre-wrap",
                  wordWrap: "break-word",
                  overflowWrap: "break-word",
                  wordBreak: "break-all",
                }}
              >
                {headerDetails}
              </code>
            </pre>
          </div>
        </>
      )}
    </div>
  );
};

export default EmailHeaders;

and finally Attachments to view attached file names.

/components/Attachments.tsx

"use client";

interface AttachmentsInterface {
  attachmentsDetails: Array<any> | undefined;
}

const AttachmentsHeaders: React.FC<AttachmentsInterface> = ({
  attachmentsDetails,
}) => {
  return (
    <div className="px-16 my-8">
      {attachmentsDetails && attachmentsDetails.length > 0 &&(
        <>
          <h3 className="text-2xl font-bold">Attachments</h3>
          <dl className="max-w-md text-gray-900 divide-y divide-gray-200 dark:text-white dark:divide-gray-700">
            {attachmentsDetails.map((item) => (
              <>
                <div className="flex flex-col pb-3">
                  <dt className="mb-1 text-gray-500 md:text-lg dark:text-gray-400">
                    {item.attachMimeTag}
                  </dt>
                  <dd className="text-lg font-semibold">{item.fileName}</dd>
                </div>
              </>
            ))}
          </dl>
        </>
      )}
    </div>
  );
};

export default AttachmentsHeaders;

Results

At the end the result will look like below:

Build MSG File Analyzer using NextJS

Build MSG File Analyzer using NextJS

What’s next?

One more project added yo your portfolio! Feel free to display more details or change the design as you want.

You need to deploy your NextJS to Vercel to make it publicly accessible for others. Also, remember to explain your solution well in ReadMe file since it shows that you’re documenting your work.