@hedhog/mail 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
- }