@hedhog/mail 0.0.5 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hedhog/mail",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "main": "dist/index.js",
5
5
  "scripts": {
6
6
  "build": "tsc --project tsconfig.production.json && npm version patch",
@@ -10,7 +10,13 @@
10
10
  "author": "HedHog",
11
11
  "license": "MIT",
12
12
  "description": "",
13
+ "files": [
14
+ "dist/**/*",
15
+ "src/migrations/**/*.ts",
16
+ "src/**/*.ejs"
17
+ ],
13
18
  "dependencies": {
19
+ "@nestjs/axios": "^3.0.3",
14
20
  "mimemessage": "1.0.5"
15
21
  },
16
22
  "peerDependencies": {
@@ -0,0 +1 @@
1
+ MailModule.register({ global: true, type: 'SMTP', host: 'changeme', port: 465, secure: false, username: 'changeme', password: 'changeme' })
@@ -1,5 +0,0 @@
1
- export enum MailConfigurationTypeEnum {
2
- AWS = 'AWS',
3
- SMTP = 'SMTP',
4
- GMAIL = 'GMAIL',
5
- }
package/src/index.ts DELETED
@@ -1,4 +0,0 @@
1
- export * from './enums/mail-configuration-type.enum';
2
- export * from './interfaces';
3
- export * from './mail.module';
4
- export * from './mail.service';
@@ -1,2 +0,0 @@
1
- export * from './mail-module-options.interface';
2
- export * from './mail-type';
@@ -1,44 +0,0 @@
1
- import { ModuleMetadata, Provider, Type } from '@nestjs/common';
2
-
3
- export type MailModuleOptions =
4
- | {
5
- global?: boolean;
6
- type: 'AWS';
7
- region: string;
8
- accessKeyId: string;
9
- secretAccessKey: string;
10
- from: string;
11
- }
12
- | {
13
- global?: boolean;
14
- type: 'SMTP';
15
- host: string;
16
- port: number;
17
- secure?: boolean;
18
- username: string;
19
- password: string;
20
- }
21
- | {
22
- global?: boolean;
23
- type: 'GMAIL';
24
- clientId: string;
25
- clientSecret: string;
26
- refreshToken: string;
27
- from: string;
28
- };
29
-
30
- export interface MailOptionsFactory {
31
- createMailOptions(): Promise<MailModuleOptions> | MailModuleOptions;
32
- }
33
-
34
- export interface MailModuleAsyncOptions
35
- extends Pick<ModuleMetadata, 'imports'> {
36
- global?: boolean;
37
- useExisting?: Type<MailOptionsFactory>;
38
- useClass?: Type<MailOptionsFactory>;
39
- useFactory?: (
40
- ...args: any[]
41
- ) => Promise<MailModuleOptions> | MailModuleOptions;
42
- inject?: any[];
43
- extraProviders?: Provider[];
44
- }
@@ -1,12 +0,0 @@
1
- export interface MailType {
2
- from?: string;
3
- to: string | string[];
4
- cc?: string | string[];
5
- bcc?: string | string[];
6
- subject?: string;
7
- body?: string;
8
- replyTo?: string | string[];
9
- priority?: string;
10
- createdAt?: string;
11
- updatedAt?: string;
12
- }
@@ -1,20 +0,0 @@
1
- import { MailType } from './interfaces/mail-type';
2
-
3
- export type MailAttachment = {
4
- filename?: string;
5
- content?: Buffer;
6
- contentType?: string;
7
- };
8
-
9
- export type Mail = {
10
- attachments?: MailAttachment[];
11
- } & MailType;
12
-
13
- export type MailConfig = {
14
- host?: string;
15
- from?: string;
16
- accessKeyId?: string;
17
- secretAccessKey?: string;
18
- };
19
-
20
- export const MAIL_MODULE_OPTIONS = 'MAIL_MODULE_OPTIONS';
@@ -1,75 +0,0 @@
1
- import { HttpModule } from '@nestjs/axios';
2
- import { DynamicModule, Module, Provider } from '@nestjs/common';
3
- import {
4
- MailModuleAsyncOptions,
5
- MailModuleOptions,
6
- MailOptionsFactory,
7
- } from './interfaces/mail-module-options.interface';
8
- import { MAIL_MODULE_OPTIONS } from './mail.consts';
9
- import { MailService } from './mail.service';
10
-
11
- @Module({
12
- imports: [HttpModule],
13
- providers: [MailService],
14
- exports: [MailService],
15
- })
16
- export class MailModule {
17
- static register(options: MailModuleOptions): DynamicModule {
18
- console.log('MailModule.register', { options });
19
- return {
20
- module: MailModule,
21
- global: options.global,
22
- providers: [
23
- {
24
- provide: MAIL_MODULE_OPTIONS,
25
- useValue: options || {},
26
- },
27
- ],
28
- };
29
- }
30
-
31
- static registerAsync(options: MailModuleAsyncOptions): DynamicModule {
32
- return {
33
- module: MailModule,
34
- global: options.global,
35
- imports: options.imports || [],
36
- providers: [
37
- ...this.createAsyncProviders(options),
38
- ...(options.extraProviders ?? []),
39
- ],
40
- };
41
- }
42
-
43
- private static createAsyncProviders(
44
- options: MailModuleAsyncOptions,
45
- ): Provider[] {
46
- if (options.useExisting || options.useFactory) {
47
- return [this.createAsyncOptionsProvider(options)];
48
- }
49
- return [
50
- this.createAsyncOptionsProvider(options),
51
- {
52
- provide: options.useClass,
53
- useClass: options.useClass,
54
- },
55
- ];
56
- }
57
-
58
- private static createAsyncOptionsProvider(
59
- options: MailModuleAsyncOptions,
60
- ): Provider {
61
- if (options.useFactory) {
62
- return {
63
- provide: MAIL_MODULE_OPTIONS,
64
- useFactory: options.useFactory,
65
- inject: options.inject || [],
66
- };
67
- }
68
- return {
69
- provide: MAIL_MODULE_OPTIONS,
70
- useFactory: async (optionsFactory: MailOptionsFactory) =>
71
- await optionsFactory.createMailOptions(),
72
- inject: [options.useExisting || options.useClass],
73
- };
74
- }
75
- }
@@ -1,18 +0,0 @@
1
- import { Test, TestingModule } from '@nestjs/testing';
2
- import { MailService } from './mail.service';
3
-
4
- describe('MailService', () => {
5
- let service: MailService;
6
-
7
- beforeEach(async () => {
8
- const module: TestingModule = await Test.createTestingModule({
9
- providers: [MailService],
10
- }).compile();
11
-
12
- service = module.get<MailService>(MailService);
13
- });
14
-
15
- it('should be defined', () => {
16
- expect(service).toBeDefined();
17
- });
18
- });
@@ -1,302 +0,0 @@
1
- import { HttpService } from '@nestjs/axios';
2
- import { Inject, Injectable } from '@nestjs/common';
3
- import * as mimemessage from 'mimemessage';
4
- import { firstValueFrom } from 'rxjs';
5
- import { MailConfigurationTypeEnum } from './enums/mail-configuration-type.enum';
6
- import { MailModuleOptions } from './interfaces/mail-module-options.interface';
7
- import { Mail, MAIL_MODULE_OPTIONS } from './mail.consts';
8
-
9
- @Injectable()
10
- export class MailService {
11
- constructor(
12
- @Inject(MAIL_MODULE_OPTIONS) private readonly mailConfig: MailModuleOptions,
13
- private readonly httpService: HttpService,
14
- ) {}
15
-
16
- async send(mail: Mail) {
17
- switch (this.mailConfig.type) {
18
- case MailConfigurationTypeEnum.AWS:
19
- return this.sendWithSES(mail);
20
-
21
- case MailConfigurationTypeEnum.GMAIL:
22
- return this.sendWithGmail(mail);
23
-
24
- case MailConfigurationTypeEnum.SMTP:
25
- return this.sendWithSMTP(mail);
26
- }
27
- }
28
-
29
- async createRawEmail(mail: Mail) {
30
- if (mail.attachments instanceof Array && mail.attachments?.length) {
31
- const mailContent = mimemessage.factory({
32
- contentType: 'multipart/mixed',
33
- body: [],
34
- });
35
-
36
- mailContent.header('From', mail.from);
37
- mailContent.header('To', mail.to);
38
- mailContent.header('Subject', mail.subject);
39
-
40
- const alternateEntity = mimemessage.factory({
41
- contentType: 'multipart/alternate',
42
- body: [],
43
- });
44
-
45
- const htmlEntity = mimemessage.factory({
46
- contentType: 'text/html;charset=utf-8',
47
- body: mail.body,
48
- });
49
-
50
- alternateEntity.body.push(htmlEntity);
51
-
52
- mailContent.body.push(alternateEntity);
53
-
54
- await Promise.all(
55
- (mail.attachments ?? []).map(async (attachment) => {
56
- const attachmentEntity = mimemessage.factory({
57
- contentType: attachment.contentType,
58
- contentTransferEncoding: 'base64',
59
- body: attachment.content
60
- .toString('base64')
61
- .replace(/([^\0]{76})/g, '$1\n'),
62
- });
63
-
64
- attachmentEntity.header(
65
- 'Content-Disposition',
66
- `attachment; filename="${attachment.filename}"`,
67
- );
68
-
69
- mailContent.body.push(attachmentEntity);
70
- }),
71
- );
72
-
73
- const messageString = mailContent.toString();
74
- const encodedMessage = Buffer.from(messageString)
75
- .toString('base64')
76
- .replace(/\+/g, '-')
77
- .replace(/\//g, '_')
78
- .replace(/=+$/, '');
79
-
80
- return encodedMessage;
81
- } else {
82
- const messageParts = [
83
- `From: ${mail.from}`,
84
- `To: ${mail.to instanceof Array ? mail.to.join(',') : mail.to}`,
85
- `Subject: ${mail.subject}`,
86
- 'Content-Type: text/plain; charset="UTF-8"',
87
- '',
88
- mail.body,
89
- ];
90
-
91
- const message = messageParts.join('\n');
92
- const encodedMessage = Buffer.from(message)
93
- .toString('base64')
94
- .replace(/\+/g, '-')
95
- .replace(/\//g, '_')
96
- .replace(/=+$/, '');
97
-
98
- return encodedMessage;
99
- }
100
- }
101
-
102
- async sendWithSMTP(mail: Mail) {
103
- if (this.mailConfig.type !== 'SMTP') {
104
- throw new Error('Invalid mail configuration type');
105
- }
106
-
107
- const {
108
- password: pass,
109
- username: user,
110
- host,
111
- port,
112
- secure = false,
113
- } = this.mailConfig;
114
-
115
- const nodemailer = await import('nodemailer');
116
-
117
- const transporter = nodemailer.createTransport({
118
- host,
119
- port,
120
- secure,
121
- auth: {
122
- user,
123
- pass,
124
- },
125
- });
126
-
127
- return transporter.sendMail({
128
- from: mail.from || process.env.SMTP_FROM || process.env.SMTP_USER,
129
- to: mail.to,
130
- subject: mail.subject,
131
- html: mail.body,
132
- cc: mail.cc,
133
- bcc: mail.bcc,
134
- replyTo: mail.replyTo,
135
- priority: mail.priority,
136
- });
137
- }
138
-
139
- async sendWithGmail(mail: Mail) {
140
- if (this.mailConfig.type !== 'GMAIL') {
141
- throw new Error('Invalid mail configuration type');
142
- }
143
- const { clientId, clientSecret, from, refreshToken } = this.mailConfig;
144
- const redirectURI = 'https://developers.google.com/oauthplayground';
145
-
146
- const { google } = await import('googleapis');
147
-
148
- const oauth2Client = new google.auth.OAuth2(
149
- clientId,
150
- clientSecret,
151
- redirectURI,
152
- );
153
-
154
- oauth2Client.setCredentials({
155
- refresh_token: refreshToken,
156
- });
157
-
158
- const { token } = await oauth2Client.getAccessToken();
159
-
160
- const raw = await this.createRawEmail({
161
- ...mail,
162
- from,
163
- });
164
-
165
- const url = 'https://www.googleapis.com/gmail/v1/users/me/messages/send';
166
-
167
- const requestBody = {
168
- raw,
169
- };
170
-
171
- const headers = {
172
- Authorization: `Bearer ${token}`,
173
- 'Content-Type': 'application/json',
174
- };
175
-
176
- const response = this.httpService.post(url, requestBody, { headers });
177
-
178
- return firstValueFrom(response);
179
- }
180
-
181
- async sendWithSES(mail: Mail) {
182
- if (this.mailConfig.type !== 'AWS') {
183
- throw new Error('Invalid mail configuration type');
184
- }
185
- const { region, from, accessKeyId, secretAccessKey } = this.mailConfig;
186
-
187
- const { SES } = await import('aws-sdk');
188
-
189
- const ses = new SES({
190
- apiVersion: '2010-12-01',
191
- region,
192
- credentials: {
193
- accessKeyId,
194
- secretAccessKey,
195
- },
196
- });
197
-
198
- ses.config.update({
199
- credentials: {
200
- accessKeyId,
201
- secretAccessKey,
202
- },
203
- });
204
-
205
- if (typeof mail.to === 'string') {
206
- mail.to = mail.to.split(';');
207
- }
208
- if (typeof mail.bcc === 'string') {
209
- mail.bcc = mail.bcc.split(';');
210
- } else if (!mail.bcc) {
211
- mail.bcc = [];
212
- }
213
- if (typeof mail.cc === 'string') {
214
- mail.cc = mail.cc.split(';');
215
- } else if (!mail.cc) {
216
- mail.cc = [];
217
- }
218
- if (typeof mail.replyTo === 'string') {
219
- mail.replyTo = mail.replyTo.split(';');
220
- } else if (!mail.replyTo) {
221
- mail.replyTo = [];
222
- }
223
-
224
- if (mail.attachments instanceof Array && mail.attachments.length > 0) {
225
- const mailContent = mimemessage.factory({
226
- contentType: 'multipart/mixed',
227
- body: [],
228
- });
229
-
230
- mailContent.header('From', from);
231
- mailContent.header('To', mail.to);
232
- mailContent.header('Subject', mail.subject);
233
-
234
- const alternateEntity = mimemessage.factory({
235
- contentType: 'multipart/alternate',
236
- body: [],
237
- });
238
-
239
- const htmlEntity = mimemessage.factory({
240
- contentType: 'text/html;charset=utf-8',
241
- body: mail.body,
242
- });
243
-
244
- alternateEntity.body.push(htmlEntity);
245
-
246
- mailContent.body.push(alternateEntity);
247
-
248
- await Promise.all(
249
- (mail.attachments ?? []).map((item) => {
250
- const attachmentEntity = mimemessage.factory({
251
- contentType: item.contentType,
252
- contentTransferEncoding: 'base64',
253
- body: item.content
254
- .toString('base64')
255
- .replace(/([^\0]{76})/g, '$1\n'),
256
- });
257
-
258
- attachmentEntity.header(
259
- 'Content-Disposition',
260
- `attachment ;filename="${item.filename}"`,
261
- );
262
-
263
- mailContent.body.push(attachmentEntity);
264
- }),
265
- );
266
-
267
- return ses
268
- .sendRawEmail({
269
- RawMessage: { Data: mailContent.toString() },
270
- })
271
- .promise();
272
- } else {
273
- const params = {
274
- Destination: {
275
- ToAddresses: mail.to,
276
- BccAddresses: mail.bcc,
277
- CcAddresses: mail.cc,
278
- },
279
- Message: {
280
- Body: {
281
- Html: {
282
- Data: mail.body,
283
- Charset: 'utf-8',
284
- },
285
- Text: {
286
- Data: mail.body,
287
- Charset: 'utf-8',
288
- },
289
- },
290
- Subject: {
291
- Data: mail.subject,
292
- Charset: 'utf-8',
293
- },
294
- },
295
- ReplyToAddresses: mail.replyTo,
296
- Source: from,
297
- };
298
-
299
- return ses.sendEmail(params).promise();
300
- }
301
- }
302
- }
package/tsconfig.lib.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "declaration": true,
5
- "outDir": "../../dist/libs/mail"
6
- },
7
- "include": ["src/**/*"],
8
- "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
9
- }
@@ -1,20 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "experimentalDecorators": true,
4
- "target": "es2017",
5
- "module": "commonjs",
6
- "lib": ["es2017", "es7", "es6"],
7
- "declaration": true,
8
- "declarationMap": true,
9
- "sourceMap": true,
10
- "outDir": "./dist",
11
- "rootDir": "./src",
12
- "strict": true,
13
- "noImplicitAny": false,
14
- "strictNullChecks": false,
15
- "allowSyntheticDefaultImports": true,
16
- "esModuleInterop": true,
17
- "emitDecoratorMetadata": true
18
- },
19
- "exclude": ["node_modules", "dist"]
20
- }