Cómo utilizar push notifications con Gmail API

Problema

Solución

Tecnologías

Arquitectura

1. Crear un nuevo proyecto en Google Cloud Platform

2. Habilita la API de Pub/Sub para ese proyecto

3. Crear un Topic

4. Otorga permisos de publisher para la API de Gmail en el Topic

5. Crear un subscription

6. Activar Gmail API en el proyecto

7. Crear las credenciales de autenticación

8. Conexión al Backend

/gmail/gmail.controller.ts
import { Controller, Post } from ‘@nestjs/common’
import { DEFAULT_RESPONSE } from ‘../util/constant’import { GmailService } from ‘./gmail.service’@Controller(‘gmail’)export class GmailController {constructor(private gmailService: GmailService) {}@Post(‘receive-push-notification’)async receivePushNotification(): Promise<{success: booleanmessage: string}> {await this.gmailService.processGmailNotification()return {success: true,message: ‘Información Recibida’,}}}
/gmail/gmail.service.ts
import { Injectable, Logger } from ‘@nestjs/common’
import { gmail_v1, google } from ‘googleapis’import { Cron, CronExpression } from ‘@nestjs/schedule’@Injectable()export class GmailService {
/gmail/gmail.service.tsdecodeDataBase64(encodedData: string): string {const buffer = Buffer.from(encodedData, ‘base64’)return buffer.toString(‘utf-8’)}
/.envCLIENT_EMAIL=xxx@xxx.gserviceaccount.comPRIVATE_KEY=xxxxxxPRIVATE_KEY_ID=xxxxxPUB_SUB_TOPIC_NAME=projects/xxx/topics/xxxEMAIL_TO_READ=alex@tinkin.one
/gmail/gmail.service.tsasync auth(): Promise<gmail_v1.Gmail> {// scopes para poder modificar y leer un correo de gmailconst scopes = [‘https://www.googleapis.com/auth/gmail.modify']// Decodificamos la PRIVATE_KEYconst decodedKey = this.decodeDataBase64(process.env.PRIVATE_KEY)try {// Utilizamos el método de autenticación de google con JWT y// retornamos la autorización de gmail.const jwtClient = new google.auth.JWT(process.env.CLIENT_EMAIL,null,decodedKey,scopes,process.env.EMAIL_TO_READ,process.env.PRIVATE_KEY_ID,)await jwtClient.authorize()google.options({ auth: jwtClient })return google.gmail({ version: ‘v1’ })} catch (error) {return error}}
/gmail/gmail.service.tsasync listEmails( gmail: gmail_v1.Gmail): Promise<gmail_v1.Schema$Message[]> {const messagesList = await gmail.users.messages.list({userId: process.env.EMAIL_TO_READ,// Número máximo de correos que se va a traer (en este caso 1)maxResults: 1,// Label desde donde se van a traer el correolabelIds: [‘INBOX’],// Query para solo traer los mensajes de test@email.com// que no estén en read, chat, draft y tampoco en trash.q: `from:test@email.com -in:read -in:chat -in:draft -in:trash`,})return messagesList.data.messages}
/gmail/gmail.service.tsasync getUserEmail(gmail: gmail_v1.Gmail,messageId: string,): Promise<gmail_v1.Schema$MessagePart> {const emailClient = await gmail.users.messages.get({userId: process.env.EMAIL_TO_READ,id: messageId,})return emailClient.data.payload}
/gmail/gmail.service.tsasync markEmailAsRead(gmail: gmail_v1.Gmail,messageId: string,): Promise<gmail_v1.Schema$Message> {const modify = await gmail.users.messages.modify({userId: process.env.EMAIL_TO_READ,id: messageId,requestBody: {removeLabelIds: [‘UNREAD’],},})
return modify.data
}
async processGmailNotification() {let emailData// Utilizamos el método de autenticaciónconst gmail = await this.auth()// Listamos el último correo de INBOXconst listEmail = await this.listEmails(gmail)if (listEmail.length) {// Obtenemos el ID del correoconst messageId = listEmail.pop().id// Obtenemos la información del nuevo correoconst emailClient = await this.getUserEmail(gmail, messageId)// Decodificamos la información que llega en el body en base64emailData = this.decodeDataBase64(emailClient.body.data)// Marcamos el correo como leídoawait this.markEmailAsRead(gmail, messageId)}// Finalmente retornamos la información del correo ya en texto plano// para poder utilizarla en otros métodos.return emailData}
@Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)async watchNotifications(): Promise<number> {const gmail = await this.auth()const watch = await gmail.users.watch({userId: process.env.EMAIL_TO_READ,requestBody: {labelIds: [‘INBOX’],topicName: process.env.PUB_SUB_TOPIC_NAME,},})return watch.status}

Conclusión

Referencias

Hey! We are Tinkin, a startup that supports other startups, advising them from the definition of their MVP to the moon.

Hey! We are Tinkin, a startup that supports other startups, advising them from the definition of their MVP to the moon.