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:
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.
PylotStuff / msg-analyzer
Extract msg file details
MSG READER
Key Features:
- MSG File Upload: Users can upload MSG files through an intuitive interface with drag-and-drop support.
- Data Extraction from MSG Files: The application uses the @kenjiuno/msgreader library to extract headers and other data from MSG files.
- Header Display: Extracted data is displayed in an easy-to-read format.
Usage Example:
- Navigate to the main page of the application.
- Upload an MSG file using the upload area.
- View the headers and content of the file displayed on the screen.
Top comments (0)