@bernierllc/email-sendgrid-plugin 1.3.0 → 1.4.0

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/__tests__/converters/attachment-header-mapping.test.ts +337 -0
  3. package/coverage/clover.xml +395 -0
  4. package/coverage/coverage-final.json +12 -0
  5. package/coverage/lcov-report/api/errors.ts.html +232 -0
  6. package/coverage/lcov-report/api/index.html +146 -0
  7. package/coverage/lcov-report/api/rate-limiter.ts.html +322 -0
  8. package/coverage/lcov-report/api/sendgrid-client.ts.html +343 -0
  9. package/coverage/lcov-report/base.css +224 -0
  10. package/coverage/lcov-report/block-navigation.js +87 -0
  11. package/coverage/lcov-report/converters/index.html +131 -0
  12. package/coverage/lcov-report/converters/sender-converter.ts.html +208 -0
  13. package/coverage/lcov-report/converters/template-converter.ts.html +376 -0
  14. package/coverage/lcov-report/favicon.png +0 -0
  15. package/coverage/lcov-report/index.html +161 -0
  16. package/coverage/lcov-report/plugin/index.html +161 -0
  17. package/coverage/lcov-report/plugin/sender-manager.ts.html +583 -0
  18. package/coverage/lcov-report/plugin/sendgrid-plugin.ts.html +1033 -0
  19. package/coverage/lcov-report/plugin/sync-manager.ts.html +430 -0
  20. package/coverage/lcov-report/plugin/template-manager.ts.html +805 -0
  21. package/coverage/lcov-report/prettify.css +1 -0
  22. package/coverage/lcov-report/prettify.js +2 -0
  23. package/coverage/lcov-report/safety/index.html +131 -0
  24. package/coverage/lcov-report/safety/ownership-validator.ts.html +250 -0
  25. package/coverage/lcov-report/safety/prefix-enforcer.ts.html +196 -0
  26. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  27. package/coverage/lcov-report/sorter.js +210 -0
  28. package/coverage/lcov.info +734 -0
  29. package/dist/converters/sender-converter.d.ts +25 -1
  30. package/dist/converters/sender-converter.js +74 -0
  31. package/dist/index.d.ts +1 -1
  32. package/dist/types/sendgrid-types.d.ts +26 -0
  33. package/package.json +15 -17
  34. package/src/converters/sender-converter.ts +98 -1
  35. package/src/index.ts +4 -1
  36. package/src/types/sendgrid-types.ts +29 -0
@@ -1,6 +1,30 @@
1
- import { VerifiedSender, SendGridVerifiedSenderData } from '../types/sendgrid-types';
1
+ import { VerifiedSender, SendGridVerifiedSenderData, EmailAttachment, SendGridAttachment, SendGridMailPayload } from '../types/sendgrid-types';
2
2
  export declare class SenderConverter {
3
3
  convertToVerifiedSender(sendgridData: SendGridVerifiedSenderData): VerifiedSender;
4
+ /**
5
+ * Convert a single EmailAttachment to SendGrid's attachment format.
6
+ */
7
+ convertAttachment(attachment: EmailAttachment): SendGridAttachment;
8
+ /**
9
+ * Convert an array of EmailAttachment objects to SendGrid format.
10
+ * Returns undefined if the array is empty or undefined (so the field
11
+ * can be omitted from the payload).
12
+ */
13
+ convertAttachments(attachments?: EmailAttachment[]): SendGridAttachment[] | undefined;
14
+ /**
15
+ * Convert custom headers to the format expected by SendGrid's v3 mail send API.
16
+ * Filters out reserved SendGrid headers that must not be overridden.
17
+ * Returns undefined if no valid headers remain.
18
+ */
19
+ convertHeaders(headers?: Record<string, string>): Record<string, string> | undefined;
20
+ /**
21
+ * Build the attachment and header portion of a SendGrid mail payload
22
+ * from canonical email data.
23
+ */
24
+ buildMailPayload(options: {
25
+ attachments?: EmailAttachment[];
26
+ headers?: Record<string, string>;
27
+ }): SendGridMailPayload;
4
28
  extractDomain(email: string): string;
5
29
  isDomainMatch(domain1: string, domain2: string): boolean;
6
30
  }
@@ -22,6 +22,80 @@ class SenderConverter {
22
22
  lastVerifiedAt: sendgridData.verified ? new Date() : undefined
23
23
  };
24
24
  }
25
+ /**
26
+ * Convert a single EmailAttachment to SendGrid's attachment format.
27
+ */
28
+ convertAttachment(attachment) {
29
+ const sgAttachment = {
30
+ content: attachment.content,
31
+ filename: attachment.filename,
32
+ };
33
+ if (attachment.contentType) {
34
+ sgAttachment.type = attachment.contentType;
35
+ }
36
+ if (attachment.disposition) {
37
+ sgAttachment.disposition = attachment.disposition;
38
+ }
39
+ return sgAttachment;
40
+ }
41
+ /**
42
+ * Convert an array of EmailAttachment objects to SendGrid format.
43
+ * Returns undefined if the array is empty or undefined (so the field
44
+ * can be omitted from the payload).
45
+ */
46
+ convertAttachments(attachments) {
47
+ if (!attachments || attachments.length === 0) {
48
+ return undefined;
49
+ }
50
+ return attachments.map(a => this.convertAttachment(a));
51
+ }
52
+ /**
53
+ * Convert custom headers to the format expected by SendGrid's v3 mail send API.
54
+ * Filters out reserved SendGrid headers that must not be overridden.
55
+ * Returns undefined if no valid headers remain.
56
+ */
57
+ convertHeaders(headers) {
58
+ if (!headers || Object.keys(headers).length === 0) {
59
+ return undefined;
60
+ }
61
+ const RESERVED_HEADERS = new Set([
62
+ 'x-sg-id',
63
+ 'x-sg-eid',
64
+ 'received',
65
+ 'dkim-signature',
66
+ 'content-type',
67
+ 'content-transfer-encoding',
68
+ 'to',
69
+ 'from',
70
+ 'subject',
71
+ 'reply-to',
72
+ 'cc',
73
+ 'bcc',
74
+ ]);
75
+ const filtered = {};
76
+ for (const [key, value] of Object.entries(headers)) {
77
+ if (!RESERVED_HEADERS.has(key.toLowerCase())) {
78
+ filtered[key] = value;
79
+ }
80
+ }
81
+ return Object.keys(filtered).length > 0 ? filtered : undefined;
82
+ }
83
+ /**
84
+ * Build the attachment and header portion of a SendGrid mail payload
85
+ * from canonical email data.
86
+ */
87
+ buildMailPayload(options) {
88
+ const payload = {};
89
+ const attachments = this.convertAttachments(options.attachments);
90
+ if (attachments) {
91
+ payload.attachments = attachments;
92
+ }
93
+ const headers = this.convertHeaders(options.headers);
94
+ if (headers) {
95
+ payload.headers = headers;
96
+ }
97
+ return payload;
98
+ }
25
99
  extractDomain(email) {
26
100
  return email.split('@')[1]?.toLowerCase() || '';
27
101
  }
package/dist/index.d.ts CHANGED
@@ -11,4 +11,4 @@ export { SenderConverter } from './converters/sender-converter';
11
11
  export { SendGridApiError, TemplateNotFoundError, UnauthorizedTemplateAccessError, TemplateConflictError, RateLimitError } from './api/errors';
12
12
  export type { SendGridPluginConfig, ApiRequestOptions, ApiResponse } from './types/config-types';
13
13
  export type { PluginConfig, ProviderCapability, Template, ProviderTemplate, TemplateVersion, SyncResult, ValidationResult, DateRange, TemplateAnalytics, FeatureConfig } from './types/plugin-types';
14
- export type { VerifiedSender, SenderValidationResult, SendGridTemplateData, SendGridTemplateVersion, SendGridVerifiedSenderData } from './types/sendgrid-types';
14
+ export type { VerifiedSender, SenderValidationResult, SendGridTemplateData, SendGridTemplateVersion, SendGridVerifiedSenderData, EmailAttachment, SendGridAttachment, SendGridMailPayload } from './types/sendgrid-types';
@@ -54,3 +54,29 @@ export interface SendGridApiError extends Error {
54
54
  statusCode?: number;
55
55
  response?: unknown;
56
56
  }
57
+ /**
58
+ * Email attachment in the canonical format (matches @bernierllc/email-sender EmailAttachment).
59
+ */
60
+ export interface EmailAttachment {
61
+ filename: string;
62
+ content: string;
63
+ contentType?: string;
64
+ disposition?: 'attachment' | 'inline';
65
+ }
66
+ /**
67
+ * SendGrid's attachment format for the v3 mail send API.
68
+ */
69
+ export interface SendGridAttachment {
70
+ content: string;
71
+ filename: string;
72
+ type?: string;
73
+ disposition?: string;
74
+ }
75
+ /**
76
+ * SendGrid mail personalization/message payload fields related to
77
+ * attachments and custom headers.
78
+ */
79
+ export interface SendGridMailPayload {
80
+ attachments?: SendGridAttachment[];
81
+ headers?: Record<string, string>;
82
+ }
package/package.json CHANGED
@@ -1,20 +1,10 @@
1
1
  {
2
2
  "name": "@bernierllc/email-sendgrid-plugin",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "deprecated": "This package is deprecated. SendGrid functionality has been merged into @bernierllc/email-sender@3.0.0+. See https://github.com/BernierLLC/tools/blob/main/packages/core/email-sender/MIGRATION.md for migration guide.",
5
5
  "description": "SendGrid-specific template management, sender verification, and advanced features plugin for email template service",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
- "scripts": {
9
- "build": "tsc",
10
- "clean": "rm -rf dist",
11
- "test": "jest",
12
- "test:run": "jest --watchAll=false",
13
- "test:watch": "jest --watch",
14
- "test:coverage": "jest --coverage",
15
- "lint": "eslint src --ext .ts",
16
- "prepublishOnly": "npm run clean && npm run build"
17
- },
18
8
  "keywords": [
19
9
  "sendgrid",
20
10
  "email",
@@ -26,10 +16,10 @@
26
16
  "author": "Bernier LLC",
27
17
  "license": "SEE LICENSE IN LICENSE",
28
18
  "dependencies": {
29
- "@bernierllc/logger": "1.3.0",
30
- "@bernierllc/rate-limiter": "0.2.0",
31
- "@bernierllc/retry-policy": "0.3.0",
32
- "@sendgrid/mail": "^8.1.3"
19
+ "@sendgrid/mail": "^8.1.3",
20
+ "@bernierllc/rate-limiter": "0.2.2",
21
+ "@bernierllc/logger": "1.3.1",
22
+ "@bernierllc/retry-policy": "0.3.1"
33
23
  },
34
24
  "devDependencies": {
35
25
  "@types/jest": "^29.5.12",
@@ -45,5 +35,13 @@
45
35
  "access": "public",
46
36
  "registry": "https://registry.npmjs.org/"
47
37
  },
48
- "gitHead": "af7e74b3715d56d3a193e1bb6743b337c2b0df6d"
49
- }
38
+ "scripts": {
39
+ "build": "tsc",
40
+ "clean": "rm -rf dist",
41
+ "test": "jest",
42
+ "test:run": "jest --watchAll=false",
43
+ "test:watch": "jest --watch",
44
+ "test:coverage": "jest --coverage",
45
+ "lint": "eslint src --ext .ts"
46
+ }
47
+ }
@@ -6,7 +6,13 @@ The client may use and modify this code *only within the scope of the project it
6
6
  Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
7
7
  */
8
8
 
9
- import { VerifiedSender, SendGridVerifiedSenderData } from '../types/sendgrid-types';
9
+ import {
10
+ VerifiedSender,
11
+ SendGridVerifiedSenderData,
12
+ EmailAttachment,
13
+ SendGridAttachment,
14
+ SendGridMailPayload
15
+ } from '../types/sendgrid-types';
10
16
 
11
17
  export class SenderConverter {
12
18
  convertToVerifiedSender(sendgridData: SendGridVerifiedSenderData): VerifiedSender {
@@ -24,6 +30,97 @@ export class SenderConverter {
24
30
  };
25
31
  }
26
32
 
33
+ /**
34
+ * Convert a single EmailAttachment to SendGrid's attachment format.
35
+ */
36
+ convertAttachment(attachment: EmailAttachment): SendGridAttachment {
37
+ const sgAttachment: SendGridAttachment = {
38
+ content: attachment.content,
39
+ filename: attachment.filename,
40
+ };
41
+
42
+ if (attachment.contentType) {
43
+ sgAttachment.type = attachment.contentType;
44
+ }
45
+
46
+ if (attachment.disposition) {
47
+ sgAttachment.disposition = attachment.disposition;
48
+ }
49
+
50
+ return sgAttachment;
51
+ }
52
+
53
+ /**
54
+ * Convert an array of EmailAttachment objects to SendGrid format.
55
+ * Returns undefined if the array is empty or undefined (so the field
56
+ * can be omitted from the payload).
57
+ */
58
+ convertAttachments(attachments?: EmailAttachment[]): SendGridAttachment[] | undefined {
59
+ if (!attachments || attachments.length === 0) {
60
+ return undefined;
61
+ }
62
+
63
+ return attachments.map(a => this.convertAttachment(a));
64
+ }
65
+
66
+ /**
67
+ * Convert custom headers to the format expected by SendGrid's v3 mail send API.
68
+ * Filters out reserved SendGrid headers that must not be overridden.
69
+ * Returns undefined if no valid headers remain.
70
+ */
71
+ convertHeaders(headers?: Record<string, string>): Record<string, string> | undefined {
72
+ if (!headers || Object.keys(headers).length === 0) {
73
+ return undefined;
74
+ }
75
+
76
+ const RESERVED_HEADERS = new Set([
77
+ 'x-sg-id',
78
+ 'x-sg-eid',
79
+ 'received',
80
+ 'dkim-signature',
81
+ 'content-type',
82
+ 'content-transfer-encoding',
83
+ 'to',
84
+ 'from',
85
+ 'subject',
86
+ 'reply-to',
87
+ 'cc',
88
+ 'bcc',
89
+ ]);
90
+
91
+ const filtered: Record<string, string> = {};
92
+ for (const [key, value] of Object.entries(headers)) {
93
+ if (!RESERVED_HEADERS.has(key.toLowerCase())) {
94
+ filtered[key] = value;
95
+ }
96
+ }
97
+
98
+ return Object.keys(filtered).length > 0 ? filtered : undefined;
99
+ }
100
+
101
+ /**
102
+ * Build the attachment and header portion of a SendGrid mail payload
103
+ * from canonical email data.
104
+ */
105
+ buildMailPayload(options: {
106
+ attachments?: EmailAttachment[];
107
+ headers?: Record<string, string>;
108
+ }): SendGridMailPayload {
109
+ const payload: SendGridMailPayload = {};
110
+
111
+ const attachments = this.convertAttachments(options.attachments);
112
+ if (attachments) {
113
+ payload.attachments = attachments;
114
+ }
115
+
116
+ const headers = this.convertHeaders(options.headers);
117
+ if (headers) {
118
+ payload.headers = headers;
119
+ }
120
+
121
+ return payload;
122
+ }
123
+
27
124
  extractDomain(email: string): string {
28
125
  return email.split('@')[1]?.toLowerCase() || '';
29
126
  }
package/src/index.ts CHANGED
@@ -54,5 +54,8 @@ export type {
54
54
  SenderValidationResult,
55
55
  SendGridTemplateData,
56
56
  SendGridTemplateVersion,
57
- SendGridVerifiedSenderData
57
+ SendGridVerifiedSenderData,
58
+ EmailAttachment,
59
+ SendGridAttachment,
60
+ SendGridMailPayload
58
61
  } from './types/sendgrid-types';
@@ -67,3 +67,32 @@ export interface SendGridApiError extends Error {
67
67
  statusCode?: number;
68
68
  response?: unknown;
69
69
  }
70
+
71
+ /**
72
+ * Email attachment in the canonical format (matches @bernierllc/email-sender EmailAttachment).
73
+ */
74
+ export interface EmailAttachment {
75
+ filename: string;
76
+ content: string; // Base64 encoded
77
+ contentType?: string;
78
+ disposition?: 'attachment' | 'inline';
79
+ }
80
+
81
+ /**
82
+ * SendGrid's attachment format for the v3 mail send API.
83
+ */
84
+ export interface SendGridAttachment {
85
+ content: string;
86
+ filename: string;
87
+ type?: string;
88
+ disposition?: string;
89
+ }
90
+
91
+ /**
92
+ * SendGrid mail personalization/message payload fields related to
93
+ * attachments and custom headers.
94
+ */
95
+ export interface SendGridMailPayload {
96
+ attachments?: SendGridAttachment[];
97
+ headers?: Record<string, string>;
98
+ }