Implementing Read/Unread Status in Gmail with NestJS and TypeORM

Abdullah Irfan
3 min readNov 22, 2023

--

Image created by Bing

This is ninth story of series Building a Robust Backend: A Comprehensive Guide Using NestJS, TypeORM, and Microservices. Our purpose is to build an email sync system for Gmail, oAuth2 for email accounts set, we can proceed with introducing features in our application. Today we are going to write methods to mark mails as read/un-read, it uses same label system as moving mails to trash, just the difference is, here we don’t get built-in function, we need to manually handle labels.

So, let’s start with our first method, we need oAuth2 object and thread ID, pass it to message modify object with action to add or remove updated labels. The code for it is:

  private async updateReadStatus(
oAuth2Client: OAuth2Client,
threadID: string,
modifyAction: { removeLabelIds?: string[]; addLabelIds?: string[] },
): Promise<void> {
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client });
await gmail.users.messages.modify({
userId: 'me',
id: threadID,
requestBody: modifyAction,
});
}

Now we need function to update status in DB, let’s update updateThreadRecord function we created in last story to handle label changes, we will add another input parameter get labels, move trash as optional parameter, the code will become:

  private async updateThreadRecord(
threadId: string,
labelChanges: { add?: string[]; remove?: string[] },
isTrash?: boolean,
): Promise<void> {
const thread = await this.gmailThreadRepository.findOne({
where: { thread_id: threadId },
});
if (!thread) {
throw new Error('Thread not found');
}

const labelIds = new Set(thread.label_ids);

// Handle label changes
labelChanges.remove?.forEach((label) => labelIds.delete(label));
labelChanges.add?.forEach((label) => labelIds.add(label));

// Handle trashing logic
if (isTrash !== undefined) {
if (isTrash) {
labelIds.add('TRASH');
labelIds.delete('INBOX');
} else {
labelIds.delete('TRASH');
labelIds.add('INBOX');
}
}

await this.gmailThreadRepository.update(
{ thread_id: threadId },
{ label_ids: Array.from(labelIds) },
);
}

We will also update this function call in functions (moveToTrash, restoreFromTrash) to handle trash operations by passing empty object, the function call update will be:

await this.updateThreadRecord(threadId, {}, false);

Now to access these functions we will define a main function that will perform respective read and un-read operations, the code for these operations will be simple function calls:

  public async markEmailAsRead(
id: string,
threadId: string,
): Promise<ResponseMessageInterface> {
try {
const token = await this.getToken(id);
if (token) {
const oAuth2Client = getOAuthClient(token);
await this.updateReadStatus(oAuth2Client, threadId, {
removeLabelIds: ['UNREAD'],
});
await this.updateThreadRecord(threadId, { remove: ['UNREAD'] });
return customMessage(HttpStatus.OK, MESSAGE.SUCCESS);
}
} catch (error) {
console.error('Error in markEmailAsRead:', error);
customMessage(HttpStatus.BAD_REQUEST, MESSAGE.BAD_REQUEST);
}
}

public async markEmailAsUnread(
id: string,
threadId: string,
): Promise<ResponseMessageInterface> {
try {
const token = await this.getToken(id);
if (token) {
const oAuth2Client = getOAuthClient(token);
await this.updateReadStatus(oAuth2Client, threadId, {
addLabelIds: ['UNREAD'],
});
await this.updateThreadRecord(threadId, { add: ['UNREAD'] });
return customMessage(HttpStatus.OK, MESSAGE.SUCCESS);
}
} catch (error) {
console.error('Error in markEmailAsUnread:', error);
customMessage(HttpStatus.BAD_REQUEST, MESSAGE.BAD_REQUEST);
}
}

Now we have operational system to mark mails read/un-read with local DB sync. In next story we will setup method to allow email attachment downloads through link. As usual, this story code is available on GitHub in feature/mark-mail-read-unread branch. If you appreciate this work, please show your support by clapping for the story and giving star on repository.

Before we conclude, here’s a handy toolset you might want to check out: The Dev’s Tools. It’s not directly related to our tutorial, but we believe it’s worth your attention. The Dev’s Tools offers an expansive suite of utilities tailored for developers, content creators, and digital enthusiasts:

  • Image Tools: Compress single or multiple images efficiently, and craft custom QR codes effortlessly.
  • JSON Tools: Validate, compare, and ensure the integrity of your JSON data.
  • Text Tools: From comparing texts, shuffling letters, and cleaning up your content, to generating random numbers and passwords, this platform has got you covered.
  • URL Tools: Ensure safe web browsing with the URL encoder and decoder.
  • Time Tools: Calculate date ranges and convert between Unix timestamps and human-readable dates seamlessly.

It’s a treasure trove of digital utilities, so do give it a visit!

--

--