@bernierllc/sender-identity-verification 1.0.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.
- package/.env.test +8 -0
- package/.eslintrc.js +30 -0
- package/README.md +376 -0
- package/__tests__/SenderIdentityVerification.test.ts +461 -0
- package/__tests__/__mocks__/fetch-mock.ts +156 -0
- package/__tests__/additional-coverage.test.ts +129 -0
- package/__tests__/additional-error-coverage.test.ts +483 -0
- package/__tests__/branch-coverage.test.ts +509 -0
- package/__tests__/config.test.ts +119 -0
- package/__tests__/error-handling.test.ts +321 -0
- package/__tests__/final-branch-coverage.test.ts +372 -0
- package/__tests__/integration.real-api.test.ts +295 -0
- package/__tests__/providers.test.ts +331 -0
- package/__tests__/service-coverage.test.ts +412 -0
- package/dist/SenderIdentityVerification.d.ts +72 -0
- package/dist/SenderIdentityVerification.js +643 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.js +38 -0
- package/dist/errors.d.ts +27 -0
- package/dist/errors.js +61 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +21 -0
- package/dist/providers/MailgunProvider.d.ts +13 -0
- package/dist/providers/MailgunProvider.js +35 -0
- package/dist/providers/SESProvider.d.ts +12 -0
- package/dist/providers/SESProvider.js +47 -0
- package/dist/providers/SMTPProvider.d.ts +12 -0
- package/dist/providers/SMTPProvider.js +30 -0
- package/dist/providers/SendGridProvider.d.ts +19 -0
- package/dist/providers/SendGridProvider.js +98 -0
- package/dist/templates/verification-email.d.ts +9 -0
- package/dist/templates/verification-email.js +67 -0
- package/dist/types.d.ts +139 -0
- package/dist/types.js +33 -0
- package/dist/utils/domain-extractor.d.ts +4 -0
- package/dist/utils/domain-extractor.js +20 -0
- package/jest.config.cjs +33 -0
- package/package.json +60 -0
- package/src/SenderIdentityVerification.ts +796 -0
- package/src/config.ts +81 -0
- package/src/errors.ts +64 -0
- package/src/global.d.ts +24 -0
- package/src/index.ts +24 -0
- package/src/providers/MailgunProvider.ts +35 -0
- package/src/providers/SESProvider.ts +51 -0
- package/src/providers/SMTPProvider.ts +29 -0
- package/src/providers/SendGridProvider.ts +108 -0
- package/src/templates/verification-email.ts +67 -0
- package/src/types.ts +163 -0
- package/src/utils/domain-extractor.ts +18 -0
- package/tsconfig.json +22 -0
package/src/config.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { IEmailDomainVerification } from './types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Configuration for sender identity verification service
|
|
13
|
+
*/
|
|
14
|
+
export interface SenderIdentityConfig {
|
|
15
|
+
// Email sender configuration (for sending verification emails)
|
|
16
|
+
emailSenderConfig: {
|
|
17
|
+
provider?: string;
|
|
18
|
+
apiKey?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Domain verification configuration
|
|
22
|
+
domainVerificationConfig: {
|
|
23
|
+
instance?: IEmailDomainVerification;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Retry configuration
|
|
27
|
+
retryConfig?: {
|
|
28
|
+
maxRetries?: number;
|
|
29
|
+
initialDelayMs?: number;
|
|
30
|
+
maxDelayMs?: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Verification settings
|
|
34
|
+
verificationBaseUrl: string;
|
|
35
|
+
verificationFromEmail: string;
|
|
36
|
+
verificationFromName: string;
|
|
37
|
+
|
|
38
|
+
// Provider API keys
|
|
39
|
+
sendgridApiKey?: string;
|
|
40
|
+
mailgunApiKey?: string;
|
|
41
|
+
sesAccessKey?: string;
|
|
42
|
+
sesSecretKey?: string;
|
|
43
|
+
sesRegion?: string;
|
|
44
|
+
|
|
45
|
+
// Database configuration
|
|
46
|
+
databaseUrl?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Load configuration from environment variables
|
|
51
|
+
*/
|
|
52
|
+
export function loadConfigFromEnv(overrides: Partial<SenderIdentityConfig> = {}): SenderIdentityConfig {
|
|
53
|
+
const config: SenderIdentityConfig = {
|
|
54
|
+
verificationBaseUrl: process.env.SENDER_VERIFICATION_BASE_URL || overrides.verificationBaseUrl || '',
|
|
55
|
+
verificationFromEmail: process.env.SENDER_VERIFICATION_FROM_EMAIL || overrides.verificationFromEmail || 'noreply@example.com',
|
|
56
|
+
verificationFromName: process.env.SENDER_VERIFICATION_FROM_NAME || overrides.verificationFromName || 'Email Verification',
|
|
57
|
+
|
|
58
|
+
sendgridApiKey: process.env.SENDGRID_API_KEY || overrides.sendgridApiKey,
|
|
59
|
+
mailgunApiKey: process.env.MAILGUN_API_KEY || overrides.mailgunApiKey,
|
|
60
|
+
sesAccessKey: process.env.AWS_SES_ACCESS_KEY || overrides.sesAccessKey,
|
|
61
|
+
sesSecretKey: process.env.AWS_SES_SECRET_KEY || overrides.sesSecretKey,
|
|
62
|
+
sesRegion: process.env.AWS_SES_REGION || overrides.sesRegion || 'us-east-1',
|
|
63
|
+
|
|
64
|
+
databaseUrl: process.env.DATABASE_URL || overrides.databaseUrl,
|
|
65
|
+
|
|
66
|
+
emailSenderConfig: overrides.emailSenderConfig || {},
|
|
67
|
+
domainVerificationConfig: overrides.domainVerificationConfig || {},
|
|
68
|
+
retryConfig: overrides.retryConfig
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Log configuration (without sensitive data)
|
|
72
|
+
console.log('⚙️ Sender Identity Verification Configuration:');
|
|
73
|
+
console.log(` └── Verification Base URL: ${config.verificationBaseUrl || '(not set)'}`);
|
|
74
|
+
console.log(` └── From Email: ${config.verificationFromEmail}`);
|
|
75
|
+
console.log(` └── SendGrid: ${config.sendgridApiKey ? '***configured***' : '(not set)'}`);
|
|
76
|
+
console.log(` └── Mailgun: ${config.mailgunApiKey ? '***configured***' : '(not set)'}`);
|
|
77
|
+
console.log(` └── SES: ${config.sesAccessKey ? '***configured***' : '(not set)'}`);
|
|
78
|
+
console.log(` └── Database: ${config.databaseUrl ? '***configured***' : '(not set)'}`);
|
|
79
|
+
|
|
80
|
+
return config;
|
|
81
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { SenderIdentityResult } from './types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Sender error codes
|
|
13
|
+
*/
|
|
14
|
+
export enum SenderErrorCode {
|
|
15
|
+
SENDER_NOT_FOUND = 'SENDER_NOT_FOUND',
|
|
16
|
+
SENDER_ALREADY_EXISTS = 'SENDER_ALREADY_EXISTS',
|
|
17
|
+
SENDER_LOCKED = 'SENDER_LOCKED',
|
|
18
|
+
VERIFICATION_EXPIRED = 'VERIFICATION_EXPIRED',
|
|
19
|
+
VERIFICATION_FAILED = 'VERIFICATION_FAILED',
|
|
20
|
+
DOMAIN_NOT_VERIFIED = 'DOMAIN_NOT_VERIFIED',
|
|
21
|
+
PROVIDER_ERROR = 'PROVIDER_ERROR',
|
|
22
|
+
INVALID_EMAIL = 'INVALID_EMAIL',
|
|
23
|
+
RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Custom error class for sender verification errors
|
|
28
|
+
*/
|
|
29
|
+
export class SenderVerificationError extends Error {
|
|
30
|
+
constructor(
|
|
31
|
+
message: string,
|
|
32
|
+
public code: SenderErrorCode,
|
|
33
|
+
public details?: unknown
|
|
34
|
+
) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = 'SenderVerificationError';
|
|
37
|
+
Object.setPrototypeOf(this, SenderVerificationError.prototype);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Error handling wrapper
|
|
43
|
+
*/
|
|
44
|
+
export function handleSenderError(error: unknown): SenderIdentityResult<never> {
|
|
45
|
+
if (error instanceof SenderVerificationError) {
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
error: error.message,
|
|
49
|
+
errors: [error.code]
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (error instanceof Error) {
|
|
54
|
+
return {
|
|
55
|
+
success: false,
|
|
56
|
+
error: error.message
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
error: 'Unknown error occurred'
|
|
63
|
+
};
|
|
64
|
+
}
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Node.js global types
|
|
10
|
+
declare const process: {
|
|
11
|
+
env: {
|
|
12
|
+
[key: string]: string | undefined;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Fetch API types (minimal declaration)
|
|
17
|
+
declare function fetch(url: string, init?: {
|
|
18
|
+
method?: string;
|
|
19
|
+
headers?: Record<string, string>;
|
|
20
|
+
body?: string;
|
|
21
|
+
}): Promise<{
|
|
22
|
+
ok: boolean;
|
|
23
|
+
json(): Promise<unknown>;
|
|
24
|
+
}>;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export { SenderIdentityVerification } from './SenderIdentityVerification.js';
|
|
10
|
+
export { SenderIdentityConfig, loadConfigFromEnv } from './config.js';
|
|
11
|
+
export { SenderVerificationError, SenderErrorCode, handleSenderError } from './errors.js';
|
|
12
|
+
export {
|
|
13
|
+
SenderIdentity,
|
|
14
|
+
SenderStatus,
|
|
15
|
+
EmailProvider,
|
|
16
|
+
CreateSenderInput,
|
|
17
|
+
UpdateSenderInput,
|
|
18
|
+
VerificationResult,
|
|
19
|
+
ComplianceCheckResult,
|
|
20
|
+
ListSendersOptions,
|
|
21
|
+
SenderIdentityResult,
|
|
22
|
+
DomainVerificationStatus,
|
|
23
|
+
IEmailDomainVerification
|
|
24
|
+
} from './types.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { SenderIdentity, SenderIdentityResult } from '../types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Mailgun provider for sender verification
|
|
13
|
+
* Note: Mailgun doesn't have sender-level verification, only domain verification
|
|
14
|
+
*/
|
|
15
|
+
export class MailgunProvider {
|
|
16
|
+
constructor(_apiKey: string) {
|
|
17
|
+
// apiKey parameter reserved for future Mailgun API integration
|
|
18
|
+
void _apiKey;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Verify sender with Mailgun
|
|
23
|
+
* Mailgun only verifies domains, not individual senders
|
|
24
|
+
*/
|
|
25
|
+
async verifySender(_sender: SenderIdentity): Promise<SenderIdentityResult<Record<string, unknown>>> {
|
|
26
|
+
// Mailgun doesn't require individual sender verification
|
|
27
|
+
// Domain verification is handled by @bernierllc/email-domain-verification
|
|
28
|
+
return {
|
|
29
|
+
success: true,
|
|
30
|
+
data: {
|
|
31
|
+
note: 'Mailgun does not require individual sender verification'
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { SenderIdentity, SenderIdentityResult } from '../types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* AWS SES provider for sender verification
|
|
13
|
+
*/
|
|
14
|
+
export class SESProvider {
|
|
15
|
+
constructor(
|
|
16
|
+
_accessKey: string,
|
|
17
|
+
_secretKey: string,
|
|
18
|
+
_region: string
|
|
19
|
+
) {
|
|
20
|
+
// Parameters reserved for future AWS SDK integration
|
|
21
|
+
void _accessKey;
|
|
22
|
+
void _secretKey;
|
|
23
|
+
void _region;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Verify sender with AWS SES
|
|
28
|
+
* TODO: Implement AWS SDK integration for SES VerifyEmailIdentity
|
|
29
|
+
*/
|
|
30
|
+
async verifySender(_sender: SenderIdentity): Promise<SenderIdentityResult<Record<string, unknown>>> {
|
|
31
|
+
try {
|
|
32
|
+
// TODO: Call SES VerifyEmailIdentity API
|
|
33
|
+
// For now, return success (stub implementation)
|
|
34
|
+
// Real implementation would use AWS SDK:
|
|
35
|
+
// const ses = new SES({ region: this.region, credentials: {...} });
|
|
36
|
+
// await ses.verifyEmailIdentity({ EmailAddress: sender.email });
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
success: true,
|
|
40
|
+
data: {
|
|
41
|
+
note: 'SES verification stubbed - AWS SDK integration pending'
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
error: error instanceof Error ? error.message : 'SES verification failed'
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { SenderIdentity, SenderIdentityResult } from '../types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* SMTP provider for sender verification
|
|
13
|
+
* Note: SMTP doesn't require provider-level verification
|
|
14
|
+
*/
|
|
15
|
+
export class SMTPProvider {
|
|
16
|
+
/**
|
|
17
|
+
* Verify sender with SMTP
|
|
18
|
+
* SMTP doesn't require provider verification - just email validation
|
|
19
|
+
*/
|
|
20
|
+
async verifySender(_sender: SenderIdentity): Promise<SenderIdentityResult<Record<string, unknown>>> {
|
|
21
|
+
// SMTP doesn't require provider-level verification
|
|
22
|
+
return {
|
|
23
|
+
success: true,
|
|
24
|
+
data: {
|
|
25
|
+
note: 'SMTP does not require provider-level verification'
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { SenderIdentity, SenderIdentityResult } from '../types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* SendGrid provider for sender verification
|
|
13
|
+
*/
|
|
14
|
+
export class SendGridProvider {
|
|
15
|
+
constructor(private apiKey: string) {}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Verify sender with SendGrid
|
|
19
|
+
*/
|
|
20
|
+
async verifySender(sender: SenderIdentity): Promise<SenderIdentityResult<{
|
|
21
|
+
providerId: string;
|
|
22
|
+
metadata: Record<string, unknown>;
|
|
23
|
+
}>> {
|
|
24
|
+
try {
|
|
25
|
+
if (!this.apiKey) {
|
|
26
|
+
return {
|
|
27
|
+
success: false,
|
|
28
|
+
error: 'SendGrid API key not configured'
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Create verified sender in SendGrid
|
|
33
|
+
const response = await this.sendGridRequest('POST', '/verified_senders', {
|
|
34
|
+
nickname: sender.name,
|
|
35
|
+
from_email: sender.email,
|
|
36
|
+
from_name: sender.name,
|
|
37
|
+
reply_to: sender.replyToEmail,
|
|
38
|
+
reply_to_name: sender.replyToName,
|
|
39
|
+
address: '123 Main St',
|
|
40
|
+
city: 'Anytown',
|
|
41
|
+
state: 'CA', // Must be 2-character state code per SendGrid API requirements
|
|
42
|
+
zip: '12345',
|
|
43
|
+
country: 'US'
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!response.success) {
|
|
47
|
+
return {
|
|
48
|
+
success: false,
|
|
49
|
+
error: 'SendGrid verification failed',
|
|
50
|
+
errors: [response.error || 'Unknown SendGrid error']
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
success: true,
|
|
56
|
+
data: {
|
|
57
|
+
providerId: response.data?.id as string || '',
|
|
58
|
+
metadata: response.data || {}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
error: error instanceof Error ? error.message : 'SendGrid verification failed'
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Make SendGrid API request
|
|
71
|
+
*/
|
|
72
|
+
private async sendGridRequest(
|
|
73
|
+
method: string,
|
|
74
|
+
path: string,
|
|
75
|
+
body?: Record<string, unknown>
|
|
76
|
+
): Promise<SenderIdentityResult<Record<string, unknown>>> {
|
|
77
|
+
try {
|
|
78
|
+
const response = await fetch(`https://api.sendgrid.com/v3${path}`, {
|
|
79
|
+
method,
|
|
80
|
+
headers: {
|
|
81
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
82
|
+
'Content-Type': 'application/json'
|
|
83
|
+
},
|
|
84
|
+
body: body ? JSON.stringify(body) : undefined
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const data = await response.json() as { errors?: Array<{ message: string }>; id?: string };
|
|
88
|
+
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
error: data.errors?.[0]?.message || 'SendGrid API error',
|
|
93
|
+
errors: data.errors?.map((e) => e.message)
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
success: true,
|
|
99
|
+
data: data as Record<string, unknown>
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
success: false,
|
|
104
|
+
error: error instanceof Error ? error.message : 'SendGrid request failed'
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { SenderIdentity } from '../types.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build HTML verification email
|
|
13
|
+
*/
|
|
14
|
+
export function buildVerificationEmailHtml(sender: SenderIdentity, verificationUrl: string): string {
|
|
15
|
+
return `
|
|
16
|
+
<!DOCTYPE html>
|
|
17
|
+
<html>
|
|
18
|
+
<head>
|
|
19
|
+
<style>
|
|
20
|
+
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
|
|
21
|
+
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
22
|
+
.button { background-color: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block; margin: 20px 0; }
|
|
23
|
+
.footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; font-size: 12px; color: #666; }
|
|
24
|
+
</style>
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
<div class="container">
|
|
28
|
+
<h1>Verify Your Sender Email Address</h1>
|
|
29
|
+
<p>Hello ${sender.name},</p>
|
|
30
|
+
<p>You've added <strong>${sender.email}</strong> as a sender email address for ${sender.provider}.</p>
|
|
31
|
+
<p>To complete the verification process, please click the button below:</p>
|
|
32
|
+
<a href="${verificationUrl}" class="button">Verify Email Address</a>
|
|
33
|
+
<p>Or copy and paste this URL into your browser:</p>
|
|
34
|
+
<p>${verificationUrl}</p>
|
|
35
|
+
<p><strong>This link will expire in 24 hours.</strong></p>
|
|
36
|
+
<div class="footer">
|
|
37
|
+
<p>If you didn't request this verification, please ignore this email.</p>
|
|
38
|
+
<p>Sender ID: ${sender.id}</p>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|
|
43
|
+
`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Build plain text verification email
|
|
48
|
+
*/
|
|
49
|
+
export function buildVerificationEmailText(sender: SenderIdentity, verificationUrl: string): string {
|
|
50
|
+
return `
|
|
51
|
+
Verify Your Sender Email Address
|
|
52
|
+
|
|
53
|
+
Hello ${sender.name},
|
|
54
|
+
|
|
55
|
+
You've added ${sender.email} as a sender email address for ${sender.provider}.
|
|
56
|
+
|
|
57
|
+
To complete the verification process, please visit:
|
|
58
|
+
|
|
59
|
+
${verificationUrl}
|
|
60
|
+
|
|
61
|
+
This link will expire in 24 hours.
|
|
62
|
+
|
|
63
|
+
If you didn't request this verification, please ignore this email.
|
|
64
|
+
|
|
65
|
+
Sender ID: ${sender.id}
|
|
66
|
+
`.trim();
|
|
67
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Sender identity record
|
|
11
|
+
*/
|
|
12
|
+
export interface SenderIdentity {
|
|
13
|
+
id: string;
|
|
14
|
+
email: string;
|
|
15
|
+
name: string;
|
|
16
|
+
replyToEmail?: string;
|
|
17
|
+
replyToName?: string;
|
|
18
|
+
domain: string;
|
|
19
|
+
provider: EmailProvider;
|
|
20
|
+
|
|
21
|
+
// Status tracking
|
|
22
|
+
status: SenderStatus;
|
|
23
|
+
isDefault: boolean;
|
|
24
|
+
isActive: boolean;
|
|
25
|
+
|
|
26
|
+
// Verification
|
|
27
|
+
verifiedAt?: Date;
|
|
28
|
+
verificationToken?: string;
|
|
29
|
+
verificationSentAt?: Date;
|
|
30
|
+
verificationExpiresAt?: Date;
|
|
31
|
+
verificationAttempts: number;
|
|
32
|
+
|
|
33
|
+
// Provider-specific data
|
|
34
|
+
providerSenderId?: string; // SendGrid verified sender ID
|
|
35
|
+
providerMetadata?: Record<string, unknown>;
|
|
36
|
+
|
|
37
|
+
// Validation
|
|
38
|
+
lastValidated?: Date;
|
|
39
|
+
validationErrors?: string[];
|
|
40
|
+
|
|
41
|
+
// Timestamps
|
|
42
|
+
createdAt: Date;
|
|
43
|
+
updatedAt: Date;
|
|
44
|
+
deletedAt?: Date;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Sender status enum
|
|
49
|
+
*/
|
|
50
|
+
export enum SenderStatus {
|
|
51
|
+
PENDING = 'pending', // Created, awaiting verification
|
|
52
|
+
VERIFICATION_SENT = 'verification_sent', // Verification email sent
|
|
53
|
+
VERIFIED = 'verified', // Email verified, ready to use
|
|
54
|
+
FAILED = 'failed', // Verification failed
|
|
55
|
+
EXPIRED = 'expired', // Verification token expired
|
|
56
|
+
LOCKED = 'locked', // Too many failed attempts
|
|
57
|
+
INACTIVE = 'inactive', // Deactivated by admin
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Email provider enum
|
|
62
|
+
*/
|
|
63
|
+
export enum EmailProvider {
|
|
64
|
+
SENDGRID = 'sendgrid',
|
|
65
|
+
MAILGUN = 'mailgun',
|
|
66
|
+
SES = 'ses',
|
|
67
|
+
SMTP = 'smtp',
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Sender creation input
|
|
72
|
+
*/
|
|
73
|
+
export interface CreateSenderInput {
|
|
74
|
+
email: string;
|
|
75
|
+
name: string;
|
|
76
|
+
replyToEmail?: string;
|
|
77
|
+
replyToName?: string;
|
|
78
|
+
provider: EmailProvider;
|
|
79
|
+
isDefault?: boolean;
|
|
80
|
+
skipVerification?: boolean; // For testing/dev
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Sender update input
|
|
85
|
+
*/
|
|
86
|
+
export interface UpdateSenderInput {
|
|
87
|
+
name?: string;
|
|
88
|
+
replyToEmail?: string;
|
|
89
|
+
replyToName?: string;
|
|
90
|
+
isDefault?: boolean;
|
|
91
|
+
isActive?: boolean;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Verification result
|
|
96
|
+
*/
|
|
97
|
+
export interface VerificationResult {
|
|
98
|
+
success: boolean;
|
|
99
|
+
senderId: string;
|
|
100
|
+
status: SenderStatus;
|
|
101
|
+
message: string;
|
|
102
|
+
verifiedAt?: Date;
|
|
103
|
+
errors?: string[];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Provider compliance check result
|
|
108
|
+
*/
|
|
109
|
+
export interface ComplianceCheckResult {
|
|
110
|
+
isCompliant: boolean;
|
|
111
|
+
checks: {
|
|
112
|
+
domainVerified: boolean;
|
|
113
|
+
spfValid: boolean;
|
|
114
|
+
dkimValid: boolean;
|
|
115
|
+
emailFormat: boolean;
|
|
116
|
+
};
|
|
117
|
+
errors: string[];
|
|
118
|
+
warnings: string[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Sender list options
|
|
123
|
+
*/
|
|
124
|
+
export interface ListSendersOptions {
|
|
125
|
+
provider?: EmailProvider;
|
|
126
|
+
status?: SenderStatus;
|
|
127
|
+
isActive?: boolean;
|
|
128
|
+
domain?: string;
|
|
129
|
+
limit?: number;
|
|
130
|
+
offset?: number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Package result wrapper
|
|
135
|
+
*/
|
|
136
|
+
export interface SenderIdentityResult<T = unknown> {
|
|
137
|
+
success: boolean;
|
|
138
|
+
data?: T;
|
|
139
|
+
error?: string;
|
|
140
|
+
errors?: string[];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Domain verification status (interface for missing @bernierllc/email-domain-verification)
|
|
145
|
+
* TODO: Replace with actual import when email-domain-verification package is available
|
|
146
|
+
*/
|
|
147
|
+
export interface DomainVerificationStatus {
|
|
148
|
+
isVerified: boolean;
|
|
149
|
+
domain: string;
|
|
150
|
+
verificationUrl?: string;
|
|
151
|
+
dnsRecords?: {
|
|
152
|
+
spf?: { isValid: boolean };
|
|
153
|
+
dkim?: { isValid: boolean };
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Email domain verification interface (stub for missing dependency)
|
|
159
|
+
* TODO: Replace with actual @bernierllc/email-domain-verification when available
|
|
160
|
+
*/
|
|
161
|
+
export interface IEmailDomainVerification {
|
|
162
|
+
getDomainStatus(domain: string): Promise<DomainVerificationStatus>;
|
|
163
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2025 Bernier LLC
|
|
3
|
+
|
|
4
|
+
This file is licensed to the client under a limited-use license.
|
|
5
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Extract domain from email address
|
|
11
|
+
*/
|
|
12
|
+
export function extractDomain(email: string): string {
|
|
13
|
+
const parts = email.split('@');
|
|
14
|
+
if (parts.length !== 2) {
|
|
15
|
+
throw new Error(`Invalid email format: ${email}`);
|
|
16
|
+
}
|
|
17
|
+
return parts[1].toLowerCase();
|
|
18
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020", "DOM"],
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"moduleResolution": "node",
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noImplicitReturns": true,
|
|
18
|
+
"noFallthroughCasesInSwitch": true
|
|
19
|
+
},
|
|
20
|
+
"include": ["src/**/*"],
|
|
21
|
+
"exclude": ["node_modules", "dist", "__tests__"]
|
|
22
|
+
}
|