glasswork 0.10.0 → 0.11.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 (93) hide show
  1. package/dist/email/compiler/code-generator.d.ts +11 -0
  2. package/dist/email/compiler/code-generator.d.ts.map +1 -0
  3. package/dist/email/compiler/code-generator.js +319 -0
  4. package/dist/email/compiler/code-generator.js.map +1 -0
  5. package/dist/email/compiler/compile-templates.d.ts +68 -0
  6. package/dist/email/compiler/compile-templates.d.ts.map +1 -0
  7. package/dist/email/compiler/compile-templates.js +156 -0
  8. package/dist/email/compiler/compile-templates.js.map +1 -0
  9. package/dist/email/compiler/compiler.d.ts +30 -0
  10. package/dist/email/compiler/compiler.d.ts.map +1 -0
  11. package/dist/email/compiler/compiler.js +510 -0
  12. package/dist/email/compiler/compiler.js.map +1 -0
  13. package/dist/email/compiler/index.d.ts +4 -0
  14. package/dist/email/compiler/index.d.ts.map +1 -0
  15. package/dist/email/compiler/index.js +3 -0
  16. package/dist/email/compiler/index.js.map +1 -0
  17. package/dist/email/compiler/tokenizer.d.ts +7 -0
  18. package/dist/email/compiler/tokenizer.d.ts.map +1 -0
  19. package/dist/email/compiler/tokenizer.js +154 -0
  20. package/dist/email/compiler/tokenizer.js.map +1 -0
  21. package/dist/email/compiler/type-extractor.d.ts +11 -0
  22. package/dist/email/compiler/type-extractor.d.ts.map +1 -0
  23. package/dist/email/compiler/type-extractor.js +296 -0
  24. package/dist/email/compiler/type-extractor.js.map +1 -0
  25. package/dist/email/compiler/types.d.ts +108 -0
  26. package/dist/email/compiler/types.d.ts.map +1 -0
  27. package/dist/email/compiler/types.js +2 -0
  28. package/dist/email/compiler/types.js.map +1 -0
  29. package/dist/email/config.d.ts +89 -0
  30. package/dist/email/config.d.ts.map +1 -0
  31. package/dist/email/config.js +55 -0
  32. package/dist/email/config.js.map +1 -0
  33. package/dist/email/email-service.d.ts +54 -0
  34. package/dist/email/email-service.d.ts.map +1 -0
  35. package/dist/email/email-service.js +94 -0
  36. package/dist/email/email-service.js.map +1 -0
  37. package/dist/email/index.d.ts +12 -0
  38. package/dist/email/index.d.ts.map +1 -0
  39. package/dist/email/index.js +23 -0
  40. package/dist/email/index.js.map +1 -0
  41. package/dist/email/template-registry.d.ts +83 -0
  42. package/dist/email/template-registry.d.ts.map +1 -0
  43. package/dist/email/template-registry.js +85 -0
  44. package/dist/email/template-registry.js.map +1 -0
  45. package/dist/email/templated-email-service.d.ts +59 -0
  46. package/dist/email/templated-email-service.d.ts.map +1 -0
  47. package/dist/email/templated-email-service.js +63 -0
  48. package/dist/email/templated-email-service.js.map +1 -0
  49. package/dist/email/transports/index.d.ts +3 -0
  50. package/dist/email/transports/index.d.ts.map +1 -0
  51. package/dist/email/transports/index.js +3 -0
  52. package/dist/email/transports/index.js.map +1 -0
  53. package/dist/email/transports/mock-transport.d.ts +74 -0
  54. package/dist/email/transports/mock-transport.d.ts.map +1 -0
  55. package/dist/email/transports/mock-transport.js +102 -0
  56. package/dist/email/transports/mock-transport.js.map +1 -0
  57. package/dist/email/transports/ses-transport.d.ts +65 -0
  58. package/dist/email/transports/ses-transport.d.ts.map +1 -0
  59. package/dist/email/transports/ses-transport.js +290 -0
  60. package/dist/email/transports/ses-transport.js.map +1 -0
  61. package/dist/email/types.d.ts +128 -0
  62. package/dist/email/types.d.ts.map +1 -0
  63. package/dist/email/types.js +2 -0
  64. package/dist/email/types.js.map +1 -0
  65. package/dist/email/webhooks/handler-factory.d.ts +41 -0
  66. package/dist/email/webhooks/handler-factory.d.ts.map +1 -0
  67. package/dist/email/webhooks/handler-factory.js +136 -0
  68. package/dist/email/webhooks/handler-factory.js.map +1 -0
  69. package/dist/email/webhooks/index.d.ts +6 -0
  70. package/dist/email/webhooks/index.d.ts.map +1 -0
  71. package/dist/email/webhooks/index.js +9 -0
  72. package/dist/email/webhooks/index.js.map +1 -0
  73. package/dist/email/webhooks/notification-parser.d.ts +45 -0
  74. package/dist/email/webhooks/notification-parser.d.ts.map +1 -0
  75. package/dist/email/webhooks/notification-parser.js +150 -0
  76. package/dist/email/webhooks/notification-parser.js.map +1 -0
  77. package/dist/email/webhooks/signature-verification.d.ts +38 -0
  78. package/dist/email/webhooks/signature-verification.d.ts.map +1 -0
  79. package/dist/email/webhooks/signature-verification.js +179 -0
  80. package/dist/email/webhooks/signature-verification.js.map +1 -0
  81. package/dist/email/webhooks/subscription-handler.d.ts +37 -0
  82. package/dist/email/webhooks/subscription-handler.d.ts.map +1 -0
  83. package/dist/email/webhooks/subscription-handler.js +99 -0
  84. package/dist/email/webhooks/subscription-handler.js.map +1 -0
  85. package/dist/email/webhooks/types.d.ts +187 -0
  86. package/dist/email/webhooks/types.d.ts.map +1 -0
  87. package/dist/email/webhooks/types.js +5 -0
  88. package/dist/email/webhooks/types.js.map +1 -0
  89. package/dist/index.d.ts +9 -0
  90. package/dist/index.d.ts.map +1 -1
  91. package/dist/index.js +22 -0
  92. package/dist/index.js.map +1 -1
  93. package/package.json +10 -2
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Email module types and interfaces
3
+ */
4
+ import type { SendEmailCommandInput } from '@aws-sdk/client-sesv2';
5
+ /**
6
+ * Attachment for an email message
7
+ */
8
+ export interface EmailAttachment {
9
+ /** Filename for the attachment */
10
+ filename: string;
11
+ /** Content of the attachment (base64 encoded or Buffer) */
12
+ content: string | Buffer;
13
+ /** MIME type of the attachment */
14
+ contentType?: string;
15
+ /** Content disposition (attachment or inline) */
16
+ disposition?: 'attachment' | 'inline';
17
+ /** Content ID for inline attachments */
18
+ contentId?: string;
19
+ }
20
+ /**
21
+ * Email message to be sent
22
+ */
23
+ export interface EmailMessage {
24
+ /** Recipient email address(es) */
25
+ to: string | string[];
26
+ /** Email subject */
27
+ subject: string;
28
+ /** HTML body content */
29
+ html: string;
30
+ /** Plain text body content (auto-generated if not provided) */
31
+ text?: string;
32
+ /** Sender email address (overrides default) */
33
+ from?: string;
34
+ /** Reply-to email address */
35
+ replyTo?: string;
36
+ /** CC recipients */
37
+ cc?: string | string[];
38
+ /** BCC recipients */
39
+ bcc?: string | string[];
40
+ /** Email attachments */
41
+ attachments?: EmailAttachment[];
42
+ /** Custom headers */
43
+ headers?: Record<string, string>;
44
+ }
45
+ /**
46
+ * Result of sending an email
47
+ */
48
+ export interface EmailResult {
49
+ /** Message ID returned by the email provider */
50
+ messageId: string;
51
+ /** Whether the send was successful */
52
+ success: boolean;
53
+ /** Additional metadata from the provider */
54
+ metadata?: Record<string, unknown>;
55
+ }
56
+ /**
57
+ * Email transport interface for sending emails
58
+ */
59
+ export interface EmailTransport {
60
+ /** Transport name for logging/debugging */
61
+ readonly name: string;
62
+ /**
63
+ * Sends an email message
64
+ * @param message - The email message to send
65
+ * @returns Promise resolving to the send result
66
+ */
67
+ send(message: EmailMessage): Promise<EmailResult>;
68
+ }
69
+ /**
70
+ * SES transport configuration
71
+ */
72
+ export interface SESTransportConfig {
73
+ /** AWS region for SES */
74
+ region: string;
75
+ /** SES configuration set name (enables delivery tracking) */
76
+ configurationSet?: string;
77
+ /** Custom endpoint (for local testing with LocalStack) */
78
+ endpoint?: string;
79
+ /** Optional pre-configured SES client (useful for testing/mocking) */
80
+ client?: {
81
+ send(command: unknown): Promise<{
82
+ MessageId?: string;
83
+ }>;
84
+ };
85
+ /** Optional SendEmailCommand constructor (useful for testing/mocking) */
86
+ sendEmailCommand?: new (input: SendEmailCommandInput) => unknown;
87
+ }
88
+ /**
89
+ * SMTP transport configuration
90
+ */
91
+ export interface SMTPTransportConfig {
92
+ /** SMTP host */
93
+ host: string;
94
+ /** SMTP port */
95
+ port: number;
96
+ /** Use TLS */
97
+ secure?: boolean;
98
+ /** Authentication credentials */
99
+ auth?: {
100
+ user: string;
101
+ pass: string;
102
+ };
103
+ }
104
+ /**
105
+ * Email module configuration
106
+ */
107
+ export interface EmailConfig {
108
+ /** Default sender email address */
109
+ from: string;
110
+ /** Default reply-to address */
111
+ replyTo?: string;
112
+ /** Transport configuration */
113
+ transport: EmailTransport;
114
+ }
115
+ /**
116
+ * Hook called after an email is successfully sent
117
+ */
118
+ export type OnSentHook = (result: EmailResult, message: EmailMessage) => Promise<void> | void;
119
+ /**
120
+ * Full email module options
121
+ */
122
+ export interface EmailModuleOptions {
123
+ /** Email configuration */
124
+ config: EmailConfig;
125
+ /** Hook called after successful send (for tracking) */
126
+ onSent?: OnSentHook;
127
+ }
128
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/email/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/email/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,41 @@
1
+ import type { MiddlewareHandler } from 'hono';
2
+ import type { CreateWebhookHandlerOptions } from './types.js';
3
+ /**
4
+ * Creates a complete SES webhook handler with signature verification,
5
+ * subscription handling, and event parsing.
6
+ *
7
+ * This is a convenience factory that combines the individual middleware
8
+ * components. For more control, use the individual utilities directly.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * import { createSESWebhookHandler } from 'glasswork';
13
+ *
14
+ * router.post('/webhooks/ses',
15
+ * createSESWebhookHandler({
16
+ * onDelivered: async (event, c) => {
17
+ * const logger = c.get('logger');
18
+ * logger.info({ messageId: event.messageId }, 'Email delivered');
19
+ * await db.email.update({
20
+ * where: { messageId: event.messageId },
21
+ * data: { status: 'DELIVERED', deliveredAt: event.timestamp },
22
+ * });
23
+ * },
24
+ * onBounced: async (event, c) => {
25
+ * await db.email.update({
26
+ * where: { messageId: event.messageId },
27
+ * data: { status: 'BOUNCED', bounceType: event.bounceType },
28
+ * });
29
+ * },
30
+ * onComplaint: async (event, c) => {
31
+ * await db.email.update({
32
+ * where: { messageId: event.messageId },
33
+ * data: { status: 'COMPLAINT' },
34
+ * });
35
+ * },
36
+ * })
37
+ * );
38
+ * ```
39
+ */
40
+ export declare function createSESWebhookHandler(options?: CreateWebhookHandlerOptions): MiddlewareHandler;
41
+ //# sourceMappingURL=handler-factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler-factory.d.ts","sourceRoot":"","sources":["../../../src/email/webhooks/handler-factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAKvD,OAAO,KAAK,EAGV,2BAA2B,EAG5B,MAAM,YAAY,CAAC;AAIpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,2BAAgC,GACxC,iBAAiB,CA8BnB"}
@@ -0,0 +1,136 @@
1
+ import { createLogger } from '../../utils/logger.js';
2
+ import { parseSESNotification } from './notification-parser.js';
3
+ import { verifySNSSignature } from './signature-verification.js';
4
+ import { handleSNSSubscription } from './subscription-handler.js';
5
+ const logger = createLogger('SES Webhook');
6
+ /**
7
+ * Creates a complete SES webhook handler with signature verification,
8
+ * subscription handling, and event parsing.
9
+ *
10
+ * This is a convenience factory that combines the individual middleware
11
+ * components. For more control, use the individual utilities directly.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * import { createSESWebhookHandler } from 'glasswork';
16
+ *
17
+ * router.post('/webhooks/ses',
18
+ * createSESWebhookHandler({
19
+ * onDelivered: async (event, c) => {
20
+ * const logger = c.get('logger');
21
+ * logger.info({ messageId: event.messageId }, 'Email delivered');
22
+ * await db.email.update({
23
+ * where: { messageId: event.messageId },
24
+ * data: { status: 'DELIVERED', deliveredAt: event.timestamp },
25
+ * });
26
+ * },
27
+ * onBounced: async (event, c) => {
28
+ * await db.email.update({
29
+ * where: { messageId: event.messageId },
30
+ * data: { status: 'BOUNCED', bounceType: event.bounceType },
31
+ * });
32
+ * },
33
+ * onComplaint: async (event, c) => {
34
+ * await db.email.update({
35
+ * where: { messageId: event.messageId },
36
+ * data: { status: 'COMPLAINT' },
37
+ * });
38
+ * },
39
+ * })
40
+ * );
41
+ * ```
42
+ */
43
+ export function createSESWebhookHandler(options = {}) {
44
+ const { verifySignature = process.env.NODE_ENV === 'production', signatureOptions, onDelivered, onBounced, onComplaint, } = options;
45
+ return async (c, _next) => {
46
+ // Step 1: Verify signature (optional, enabled by default in production)
47
+ const verifyResult = await handleSignatureVerification(c, verifySignature, signatureOptions);
48
+ if (verifyResult)
49
+ return verifyResult;
50
+ // Step 2: Handle subscription confirmations
51
+ const subscriptionResult = await handleSubscriptionConfirmation(c);
52
+ if (subscriptionResult)
53
+ return subscriptionResult;
54
+ // Step 3: Parse the SES event
55
+ const event = await parseSESNotification(c);
56
+ if (!event) {
57
+ // Not an SES notification we handle (e.g., Send event)
58
+ return c.json({ received: true }, 200);
59
+ }
60
+ // Step 4: Route to the appropriate handler
61
+ await routeSESEvent(event, c, { onDelivered, onBounced, onComplaint });
62
+ return c.json({ received: true }, 200);
63
+ };
64
+ }
65
+ /**
66
+ * Handles signature verification step
67
+ */
68
+ async function handleSignatureVerification(c, verifySignature, signatureOptions) {
69
+ if (verifySignature) {
70
+ const verifyMiddleware = verifySNSSignature(signatureOptions);
71
+ return await runMiddleware(verifyMiddleware, c);
72
+ }
73
+ // Still parse the body even without verification
74
+ try {
75
+ const body = await c.req.text();
76
+ c.set('snsMessageRaw', body);
77
+ c.set('snsMessage', JSON.parse(body));
78
+ }
79
+ catch {
80
+ return c.json({ error: 'Invalid JSON body' }, 400);
81
+ }
82
+ return undefined;
83
+ }
84
+ /**
85
+ * Handles subscription confirmation step
86
+ */
87
+ async function handleSubscriptionConfirmation(c) {
88
+ const subscriptionMiddleware = handleSNSSubscription();
89
+ return await runMiddleware(subscriptionMiddleware, c);
90
+ }
91
+ /**
92
+ * Routes SES events to appropriate handlers
93
+ */
94
+ async function routeSESEvent(event, c, handlers) {
95
+ try {
96
+ switch (event.type) {
97
+ case 'delivered':
98
+ if (handlers.onDelivered) {
99
+ await handlers.onDelivered(event, c);
100
+ }
101
+ break;
102
+ case 'bounced':
103
+ if (handlers.onBounced) {
104
+ await handlers.onBounced(event, c);
105
+ }
106
+ break;
107
+ case 'complaint':
108
+ if (handlers.onComplaint) {
109
+ await handlers.onComplaint(event, c);
110
+ }
111
+ break;
112
+ }
113
+ }
114
+ catch (error) {
115
+ logger.error('Handler error:', error);
116
+ // Don't fail the request - we don't want SNS to retry indefinitely
117
+ // The error is logged and can be tracked
118
+ }
119
+ }
120
+ /**
121
+ * Helper to run middleware and capture early responses
122
+ */
123
+ async function runMiddleware(middleware, c) {
124
+ let earlyResponse;
125
+ let nextCalled = false;
126
+ await middleware(c, async () => {
127
+ nextCalled = true;
128
+ });
129
+ // Check if middleware returned early (didn't call next)
130
+ if (!nextCalled) {
131
+ // The middleware returned a response
132
+ earlyResponse = c.res;
133
+ }
134
+ return earlyResponse;
135
+ }
136
+ //# sourceMappingURL=handler-factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler-factory.js","sourceRoot":"","sources":["../../../src/email/webhooks/handler-factory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AASlE,MAAM,MAAM,GAAG,YAAY,CAAC,aAAa,CAAC,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,UAAU,uBAAuB,CACrC,UAAuC,EAAE;IAEzC,MAAM,EACJ,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EACvD,gBAAgB,EAChB,WAAW,EACX,SAAS,EACT,WAAW,GACZ,GAAG,OAAO,CAAC;IAEZ,OAAO,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE;QACxB,wEAAwE;QACxE,MAAM,YAAY,GAAG,MAAM,2BAA2B,CAAC,CAAC,EAAE,eAAe,EAAE,gBAAgB,CAAC,CAAC;QAC7F,IAAI,YAAY;YAAE,OAAO,YAAY,CAAC;QAEtC,4CAA4C;QAC5C,MAAM,kBAAkB,GAAG,MAAM,8BAA8B,CAAC,CAAC,CAAC,CAAC;QACnE,IAAI,kBAAkB;YAAE,OAAO,kBAAkB,CAAC;QAElD,8BAA8B;QAC9B,MAAM,KAAK,GAAG,MAAM,oBAAoB,CAAC,CAAC,CAAC,CAAC;QAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,uDAAuD;YACvD,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;QACzC,CAAC;QAED,2CAA2C;QAC3C,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC;QAEvE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,CAAC,CAAC;IACzC,CAAC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,2BAA2B,CACxC,CAAU,EACV,eAAwB,EACxB,gBAA2D;IAE3D,IAAI,eAAe,EAAE,CAAC;QACpB,MAAM,gBAAgB,GAAG,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;QAC9D,OAAO,MAAM,aAAa,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC;IAClD,CAAC;IAED,iDAAiD;IACjD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QAChC,CAAC,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;QAC7B,CAAC,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,EAAE,GAAG,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,8BAA8B,CAAC,CAAU;IACtD,MAAM,sBAAsB,GAAG,qBAAqB,EAAE,CAAC;IACvD,OAAO,MAAM,aAAa,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAAC;AACxD,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,aAAa,CAC1B,KAAe,EACf,CAAU,EACV,QAIC;IAED,IAAI,CAAC;QACH,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,WAAW;gBACd,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;oBACzB,MAAM,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;gBACvC,CAAC;gBACD,MAAM;YACR,KAAK,SAAS;gBACZ,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;oBACvB,MAAM,QAAQ,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;gBACrC,CAAC;gBACD,MAAM;YACR,KAAK,WAAW;gBACd,IAAI,QAAQ,CAAC,WAAW,EAAE,CAAC;oBACzB,MAAM,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;gBACvC,CAAC;gBACD,MAAM;QACV,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;QACtC,mEAAmE;QACnE,yCAAyC;IAC3C,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,aAAa,CAC1B,UAA6B,EAC7B,CAAU;IAEV,IAAI,aAAmC,CAAC;IACxC,IAAI,UAAU,GAAG,KAAK,CAAC;IAEvB,MAAM,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI,EAAE;QAC7B,UAAU,GAAG,IAAI,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,wDAAwD;IACxD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,qCAAqC;QACrC,aAAa,GAAG,CAAC,CAAC,GAAG,CAAC;IACxB,CAAC;IAED,OAAO,aAAa,CAAC;AACvB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { createSESWebhookHandler } from './handler-factory.js';
2
+ export { parseSESNotification, parseSNSMessage } from './notification-parser.js';
3
+ export { clearCertCache, verifySNSSignature, } from './signature-verification.js';
4
+ export { type HandleSubscriptionOptions, handleSNSSubscription, } from './subscription-handler.js';
5
+ export type { BouncedEvent, ComplaintEvent, CreateWebhookHandlerOptions, DeliveredEvent, SESBounceNotification, SESComplaintNotification, SESDeliveryNotification, SESEvent, SESMailObject, SESNotification, SESWebhookHandlers, SNSMessage, VerifySignatureOptions, } from './types.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/email/webhooks/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAE/D,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEjF,OAAO,EACL,cAAc,EACd,kBAAkB,GACnB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,KAAK,yBAAyB,EAC9B,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,YAAY,EACZ,cAAc,EACd,2BAA2B,EAC3B,cAAc,EACd,qBAAqB,EACrB,wBAAwB,EACxB,uBAAuB,EACvB,QAAQ,EACR,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,UAAU,EACV,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,9 @@
1
+ // Webhook types
2
+ // Handler factory
3
+ export { createSESWebhookHandler } from './handler-factory.js';
4
+ // Parsers
5
+ export { parseSESNotification, parseSNSMessage } from './notification-parser.js';
6
+ // Middleware
7
+ export { clearCertCache, verifySNSSignature, } from './signature-verification.js';
8
+ export { handleSNSSubscription, } from './subscription-handler.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/email/webhooks/index.ts"],"names":[],"mappings":"AAAA,gBAAgB;AAEhB,kBAAkB;AAClB,OAAO,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,UAAU;AACV,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AACjF,aAAa;AACb,OAAO,EACL,cAAc,EACd,kBAAkB,GACnB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAEL,qBAAqB,GACtB,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,45 @@
1
+ import type { Context } from 'hono';
2
+ import type { SESEvent, SNSMessage } from './types.js';
3
+ /**
4
+ * Parses an SES notification from an SNS message.
5
+ *
6
+ * Returns null if the message is not a notification (e.g., subscription confirmation)
7
+ * or if it's not an SES event we handle.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { parseSESNotification, verifySNSSignature, handleSNSSubscription } from 'glasswork';
12
+ *
13
+ * router.post('/webhooks/ses',
14
+ * verifySNSSignature(),
15
+ * handleSNSSubscription(),
16
+ * async (c) => {
17
+ * const event = await parseSESNotification(c);
18
+ *
19
+ * if (!event) {
20
+ * return c.json({ received: true });
21
+ * }
22
+ *
23
+ * switch (event.type) {
24
+ * case 'delivered':
25
+ * console.log('Email delivered:', event.messageId);
26
+ * break;
27
+ * case 'bounced':
28
+ * console.log('Email bounced:', event.messageId, event.bounceType);
29
+ * break;
30
+ * case 'complaint':
31
+ * console.log('Complaint received:', event.messageId);
32
+ * break;
33
+ * }
34
+ *
35
+ * return c.json({ received: true });
36
+ * }
37
+ * );
38
+ * ```
39
+ */
40
+ export declare function parseSESNotification(c: Context): Promise<SESEvent | null>;
41
+ /**
42
+ * Lower-level function to parse just the SNS message
43
+ */
44
+ export declare function parseSNSMessage(c: Context): Promise<SNSMessage>;
45
+ //# sourceMappingURL=notification-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-parser.d.ts","sourceRoot":"","sources":["../../../src/email/webhooks/notification-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,KAAK,EAOV,QAAQ,EACR,UAAU,EACX,MAAM,YAAY,CAAC;AAIpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CA8C/E;AAgED;;GAEG;AACH,wBAAsB,eAAe,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAWrE"}
@@ -0,0 +1,150 @@
1
+ import { createLogger } from '../../utils/logger.js';
2
+ const logger = createLogger('SES');
3
+ /**
4
+ * Parses an SES notification from an SNS message.
5
+ *
6
+ * Returns null if the message is not a notification (e.g., subscription confirmation)
7
+ * or if it's not an SES event we handle.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * import { parseSESNotification, verifySNSSignature, handleSNSSubscription } from 'glasswork';
12
+ *
13
+ * router.post('/webhooks/ses',
14
+ * verifySNSSignature(),
15
+ * handleSNSSubscription(),
16
+ * async (c) => {
17
+ * const event = await parseSESNotification(c);
18
+ *
19
+ * if (!event) {
20
+ * return c.json({ received: true });
21
+ * }
22
+ *
23
+ * switch (event.type) {
24
+ * case 'delivered':
25
+ * console.log('Email delivered:', event.messageId);
26
+ * break;
27
+ * case 'bounced':
28
+ * console.log('Email bounced:', event.messageId, event.bounceType);
29
+ * break;
30
+ * case 'complaint':
31
+ * console.log('Complaint received:', event.messageId);
32
+ * break;
33
+ * }
34
+ *
35
+ * return c.json({ received: true });
36
+ * }
37
+ * );
38
+ * ```
39
+ */
40
+ export async function parseSESNotification(c) {
41
+ // Try to get the pre-parsed message from context
42
+ let snsMessage = c.get('snsMessage');
43
+ if (!snsMessage) {
44
+ // Parse from body
45
+ try {
46
+ const rawBody = c.get('snsMessageRaw');
47
+ const body = rawBody || (await c.req.text());
48
+ snsMessage = JSON.parse(body);
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ // Only handle notifications
55
+ if (snsMessage.Type !== 'Notification') {
56
+ return null;
57
+ }
58
+ // Parse the inner SES notification
59
+ let notification;
60
+ try {
61
+ notification = JSON.parse(snsMessage.Message);
62
+ }
63
+ catch {
64
+ logger.error('Failed to parse notification message');
65
+ return null;
66
+ }
67
+ // Route to the appropriate parser based on notification type
68
+ // SES can use either notificationType (older) or eventType (newer format)
69
+ const eventType = notification.notificationType ||
70
+ notification.eventType;
71
+ switch (eventType) {
72
+ case 'Delivery':
73
+ return parseDeliveryNotification(notification);
74
+ case 'Bounce':
75
+ return parseBounceNotification(notification);
76
+ case 'Complaint':
77
+ return parseComplaintNotification(notification);
78
+ default:
79
+ // Send event is ignored (we track via onSent hook)
80
+ return null;
81
+ }
82
+ }
83
+ /**
84
+ * Parses a delivery notification into a DeliveredEvent
85
+ */
86
+ function parseDeliveryNotification(notification) {
87
+ const { mail, delivery } = notification;
88
+ const recipient = delivery.recipients[0] || mail.destination[0];
89
+ return {
90
+ type: 'delivered',
91
+ messageId: mail.messageId,
92
+ timestamp: new Date(delivery.timestamp),
93
+ recipient,
94
+ processingTimeMs: delivery.processingTimeMillis,
95
+ smtpResponse: delivery.smtpResponse,
96
+ raw: notification,
97
+ };
98
+ }
99
+ /**
100
+ * Parses a bounce notification into a BouncedEvent
101
+ */
102
+ function parseBounceNotification(notification) {
103
+ const { mail, bounce } = notification;
104
+ const recipient = bounce.bouncedRecipients[0]?.emailAddress || mail.destination[0];
105
+ const bounceType = bounce.bounceType === 'Permanent' ? 'permanent' : 'transient';
106
+ // Build a human-readable reason
107
+ const diagnosticCode = bounce.bouncedRecipients[0]?.diagnosticCode || 'Unknown reason';
108
+ const reason = `${bounce.bounceSubType}: ${diagnosticCode}`;
109
+ return {
110
+ type: 'bounced',
111
+ messageId: mail.messageId,
112
+ timestamp: new Date(bounce.timestamp),
113
+ recipient,
114
+ bounceType,
115
+ bounceSubType: bounce.bounceSubType,
116
+ reason,
117
+ raw: notification,
118
+ };
119
+ }
120
+ /**
121
+ * Parses a complaint notification into a ComplaintEvent
122
+ */
123
+ function parseComplaintNotification(notification) {
124
+ const { mail, complaint } = notification;
125
+ const recipient = complaint.complainedRecipients[0]?.emailAddress || mail.destination[0];
126
+ return {
127
+ type: 'complaint',
128
+ messageId: mail.messageId,
129
+ timestamp: new Date(complaint.timestamp),
130
+ recipient,
131
+ complaintType: complaint.complaintFeedbackType || 'unknown',
132
+ userAgent: complaint.userAgent,
133
+ raw: notification,
134
+ };
135
+ }
136
+ /**
137
+ * Lower-level function to parse just the SNS message
138
+ */
139
+ export async function parseSNSMessage(c) {
140
+ // Try to get from context first
141
+ const cached = c.get('snsMessage');
142
+ if (cached) {
143
+ return cached;
144
+ }
145
+ // Parse from body
146
+ const rawBody = c.get('snsMessageRaw');
147
+ const body = rawBody || (await c.req.text());
148
+ return JSON.parse(body);
149
+ }
150
+ //# sourceMappingURL=notification-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-parser.js","sourceRoot":"","sources":["../../../src/email/webhooks/notification-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { MiddlewareHandler } from 'hono';
2
+ import type { SNSMessage, VerifySignatureOptions } from './types.js';
3
+ /**
4
+ * Builds the canonical string to sign for SNS message verification
5
+ *
6
+ * Ordering differs per message type:
7
+ * - Notification: Message, MessageId, optional Subject, Timestamp, TopicArn, Type
8
+ * - SubscriptionConfirmation/UnsubscribeConfirmation: Message, MessageId, SubscribeURL, Timestamp, Token, TopicArn, Type
9
+ *
10
+ * See: https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html
11
+ */
12
+ export declare function buildStringToSign(message: SNSMessage): string;
13
+ /**
14
+ * Middleware that verifies AWS SNS message signatures.
15
+ *
16
+ * Protects webhook endpoints from spoofed requests by validating
17
+ * that messages are genuinely from AWS SNS.
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * import { verifySNSSignature } from 'glasswork';
22
+ *
23
+ * router.post('/webhooks/ses',
24
+ * verifySNSSignature(),
25
+ * async (c) => {
26
+ * // Message is verified to be from AWS SNS
27
+ * const body = await c.req.json();
28
+ * // ...
29
+ * }
30
+ * );
31
+ * ```
32
+ */
33
+ export declare function verifySNSSignature(options?: VerifySignatureOptions): MiddlewareHandler;
34
+ /**
35
+ * Clears the certificate cache (useful for testing)
36
+ */
37
+ export declare function clearCertCache(): void;
38
+ //# sourceMappingURL=signature-verification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signature-verification.d.ts","sourceRoot":"","sources":["../../../src/email/webhooks/signature-verification.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAE9C,OAAO,KAAK,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAgErE;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAmC7D;AAqBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,sBAA2B,GAAG,iBAAiB,CA6C1F;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAErC"}