Mailer

Here you will learn how our mail service built.

Currently we can have one of two email services. Both required additional packages installed, .env setup, also you must register in their services. They have pretty similar approach. They have their own documentation with this service, so we will not repeat ourselves here, but just consider how it works for us.

AWS

At this link you will read all the necessary information about how to install AWS into our project. Here we just mentioned about AWS email service.

Here you can read the official documentation about their email service.

Before we start you must set the .env constants with you AWS SES User Credentials:

  • EMAIL_SERVICE

  • EMAIL_SENDER

  • AWS_SES_REGION

  • AWS_SES_ACCESS_KEY

  • AWS_SES_SECRET_ACCESS_KEY

SendGrid

Same all for SendGrid. Just follow their guide from documentation.

.env:

  • EMAIL_SERVICE

  • EMAIL_SENDER

  • SENDGRID_API_KEY

N.B. if you cannot find such constants in .env file, than it can be found in .../src/constants.ts. This was done within the security framework if somewhere something becomes unavailable.

N.B. EMAIL_SERVICE and EMAIL_SENDER are the place were we specify what kind of service we will use. This constants are common to both services.

Structure

Pretty simple and common form for Nest.JS. Email have it's own module where all the magic happens.

...api/src/email/

But there you can find another folder for AWS and SendGrid services separately, email.service.ts uses one of those.

In .../api/src/common/interfaces/ you will find interfaces for email templates.

Email templates lays in .../api/email-templates/

All this must done/created before sending email. After you do this, just add the method into the service file and use it where you need it. For example invitation email that we've mentioned in Team Management.

    async createInvitation(body: InvitationDto, user: JwtUserPayload) {
        if (body.role === TeamRole.Owner) {
            throw new HttpException(
                {
                    message: 'Error',
                    description: "You can't add owner to an existing team",
                    code: 'ADD_OWNER_TO_TEAM',
                },
                HttpStatus.BAD_REQUEST
            );
        }

        const teamId = body.teamId || user.teamId;

        const team = await this.teamEntityRepository.findOne({
            where: { id: teamId },
            relations: ['memberships'],
        });

        const userMembership = await this.teamMembershipRepository.findOne({
            where: { team: { id: teamId }, user: { id: user.id } },
        });

        // we should check permission here, because when the user try to invite people
        // immediately after team creation he doesn't have team id and guard can't check permission
        if (!userMembership || userMembership.role === TeamRole.Viewer) {
            throw new ForbiddenException('Access denied');
        }

        const invitedUser = await this.userRepository.findOne({ where: { email: body.email }, withDeleted: true });

        if (!team) {
            throw new HttpException(
                {
                    message: 'Error',
                    description: 'Team not found',
                    code: 'TEAM_NOT_FOUND',
                },
                HttpStatus.BAD_REQUEST
            );
        }

        if (team.usersLimit <= team.memberships.length) {
            throw new HttpException(
                {
                    message: 'Error',
                    description: 'Team is full',
                    code: 'TEAM_FULL',
                },
                HttpStatus.BAD_REQUEST
            );
        }

        if (invitedUser) {
            const isMembershipValid = await this.teamMembershipService.validateMembership(teamId, invitedUser.id);

            if (isMembershipValid) {
                throw new HttpException(
                    {
                        message: 'Error',
                        description: 'User is already a member of this team',
                        code: 'USER_ALREADY_MEMBER',
                    },
                    HttpStatus.BAD_REQUEST
                );
            }

            await this.teamMembershipService.create(
                { id: invitedUser.id, firstName: body.firstName, lastName: body.lastName, phone: body.phone },
                teamId,
                body.role
            );

            this.emailService.sendExistedUserInvitationEmail(body.email, team.name);

            return {
                message: `Invitation email send`,
                description: 'Wait for invited person to finish his registration',
            };
        }

        const invite = await this.userService.createInvitedUser(body);

        await this.teamMembershipService.create(
            { id: invite.user.id, firstName: body.firstName, lastName: body.lastName, phone: body.phone },
            team.id,
            body.role
        );

        this.emailService.sendInvitationEmail(body.email, invite.token, team.name);

        return {
            message: `Invitation email send`,
            description: 'Wait for invited person to finish his registration',
        };
    }

Here you can find event two different emails that we send in two different situations.

Conclusions

  1. Register a user account on one or both services.

  2. Install all the necessary packages.

  3. Setup .env file and constants.ts

  4. Create email templates, interfaces for that templates and module for email service.

  5. Use email method where you need.

Last updated