@stacksjs/email 0.70.23 → 0.70.26
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/README.md +31 -31
- package/dist/index.js +13 -15324
- package/dist/src/drivers/index.d.ts +6 -0
- package/dist/src/drivers/log.d.ts +30 -0
- package/dist/src/drivers/mailgun.d.ts +7 -0
- package/dist/src/drivers/mailtrap.d.ts +8 -0
- package/dist/src/drivers/nodemailer.d.ts +3 -0
- package/dist/src/drivers/sendgrid.d.ts +8 -0
- package/dist/src/drivers/ses.d.ts +9 -0
- package/dist/src/email.d.ts +39 -0
- package/dist/src/index.d.ts +5 -0
- package/dist/src/mailable.d.ts +81 -0
- package/dist/src/template.d.ts +55 -0
- package/dist/{types.d.ts → src/types.d.ts} +3 -3
- package/package.json +27 -11
- package/dist/base.d.ts +0 -130
- package/dist/config.d.ts +0 -3
- package/dist/email.d.ts +0 -2
- package/dist/index.d.ts +0 -3
- package/dist/mailgun.d.ts +0 -141
- package/dist/mailtrap.d.ts +0 -131
- package/dist/sendgrid.d.ts +0 -146
- package/dist/ses.d.ts +0 -80
- package/dist/template.d.ts +0 -7
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BaseEmailDriver } from './base';
|
|
2
|
+
import type { EmailMessage, EmailResult } from '@stacksjs/types';
|
|
3
|
+
import type { TemplateOptions } from '../template';
|
|
4
|
+
declare const captured: CapturedEmail[];
|
|
5
|
+
/**
|
|
6
|
+
* Local-only email driver that never opens a network socket. Renders
|
|
7
|
+
* the message to disk so devs can inspect it (and tests can read it),
|
|
8
|
+
* and remembers the last N sends in-memory so tests can assert against
|
|
9
|
+
* them without scraping log output.
|
|
10
|
+
*
|
|
11
|
+
* Pick this driver when:
|
|
12
|
+
* - running tests (no SMTP credentials, deterministic output)
|
|
13
|
+
* - local development (no AWS/SendGrid setup, no mailbox spam)
|
|
14
|
+
* - CI smoke tests where we want to assert "an email was sent"
|
|
15
|
+
*
|
|
16
|
+
* In production, use `ses` / `sendgrid` / `mailgun` / `smtp` instead.
|
|
17
|
+
*/
|
|
18
|
+
declare interface CapturedEmail extends EmailMessage {
|
|
19
|
+
sentAt: Date
|
|
20
|
+
rendered?: { html?: string, text?: string }
|
|
21
|
+
}
|
|
22
|
+
export declare class LogEmailDriver extends BaseEmailDriver {
|
|
23
|
+
name: string;
|
|
24
|
+
send(message: EmailMessage, options?: TemplateOptions): Promise<EmailResult>;
|
|
25
|
+
static captured(): readonly CapturedEmail[];
|
|
26
|
+
static reset(): void;
|
|
27
|
+
}
|
|
28
|
+
// Convenience export to mirror the other drivers' module shape — the
|
|
29
|
+
// drivers/index.ts re-exports each driver namespace (`export * as log`).
|
|
30
|
+
export default LogEmailDriver;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BaseEmailDriver } from './base';
|
|
2
|
+
import type { EmailMessage, EmailResult } from '@stacksjs/types';
|
|
3
|
+
import type { TemplateOptions } from '../template';
|
|
4
|
+
export declare class MailgunDriver extends BaseEmailDriver {
|
|
5
|
+
name: string;
|
|
6
|
+
send(message: EmailMessage, options?: TemplateOptions): Promise<EmailResult>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BaseEmailDriver } from './base';
|
|
2
|
+
import type { EmailMessage, EmailResult } from '@stacksjs/types';
|
|
3
|
+
import type { TemplateOptions } from '../template';
|
|
4
|
+
export declare class MailtrapDriver extends BaseEmailDriver {
|
|
5
|
+
name: string;
|
|
6
|
+
send(message: EmailMessage, options?: TemplateOptions): Promise<EmailResult>;
|
|
7
|
+
}
|
|
8
|
+
export default MailtrapDriver;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BaseEmailDriver } from './base';
|
|
2
|
+
import type { EmailMessage, EmailResult } from '@stacksjs/types';
|
|
3
|
+
import type { TemplateOptions } from '../template';
|
|
4
|
+
export declare class SendGridDriver extends BaseEmailDriver {
|
|
5
|
+
name: string;
|
|
6
|
+
send(message: EmailMessage, options?: TemplateOptions): Promise<EmailResult>;
|
|
7
|
+
}
|
|
8
|
+
export default SendGridDriver;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BaseEmailDriver } from './base';
|
|
2
|
+
import type { EmailMessage, EmailResult } from '@stacksjs/types';
|
|
3
|
+
import type { TemplateOptions } from '../template';
|
|
4
|
+
export declare class SESDriver extends BaseEmailDriver {
|
|
5
|
+
name: string;
|
|
6
|
+
send(message: EmailMessage, options?: TemplateOptions): Promise<EmailResult>;
|
|
7
|
+
protected formatAddresses(addresses: string | string[] | { address: string, name?: string }[] | undefined): string[];
|
|
8
|
+
}
|
|
9
|
+
export default SESDriver;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { EmailMessage, EmailResult } from '@stacksjs/types';
|
|
2
|
+
import type { Message } from './types';
|
|
3
|
+
export declare const mail: Mail;
|
|
4
|
+
/** Result returned by email handler callbacks */
|
|
5
|
+
declare interface EmailHandlerResult {
|
|
6
|
+
message: string
|
|
7
|
+
}
|
|
8
|
+
/** Configuration for the sender address */
|
|
9
|
+
declare interface EmailFromAddress {
|
|
10
|
+
name: string
|
|
11
|
+
address: string
|
|
12
|
+
}
|
|
13
|
+
/** Configuration for the Mail singleton */
|
|
14
|
+
declare interface MailConfig {
|
|
15
|
+
defaultDriver?: string
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Email notification class for defining email notifications
|
|
19
|
+
*/
|
|
20
|
+
export declare class Email {
|
|
21
|
+
name: string;
|
|
22
|
+
subject: string;
|
|
23
|
+
to: string | string[];
|
|
24
|
+
from?: EmailFromAddress;
|
|
25
|
+
template: string;
|
|
26
|
+
handle?: () => Promise<EmailHandlerResult>;
|
|
27
|
+
onError?: (error: Error) => Promise<EmailHandlerResult>;
|
|
28
|
+
onSuccess?: () => void;
|
|
29
|
+
constructor(options: Message);
|
|
30
|
+
send(to?: string | string[]): Promise<EmailHandlerResult>;
|
|
31
|
+
}
|
|
32
|
+
declare class Mail {
|
|
33
|
+
constructor(options?: MailConfig);
|
|
34
|
+
send(message: EmailMessage): Promise<EmailResult>;
|
|
35
|
+
use(driver: string): Mail;
|
|
36
|
+
queue(message: EmailMessage): Promise<void>;
|
|
37
|
+
later(delaySeconds: number, message: EmailMessage): Promise<void>;
|
|
38
|
+
queueOn(queueName: string, message: EmailMessage): Promise<void>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { EmailAddress, EmailAttachment, EmailResult } from '@stacksjs/types';
|
|
2
|
+
/**
|
|
3
|
+
* Options accepted by {@link Mailable.send} — currently allows scoping
|
|
4
|
+
* the send to a specific driver registered on the Mail singleton (e.g.
|
|
5
|
+
* `'log'` to swallow the email in a test, `'ses'` to force production
|
|
6
|
+
* delivery during a one-off backfill).
|
|
7
|
+
*/
|
|
8
|
+
export declare interface MailableSendOptions {
|
|
9
|
+
driver?: string
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Internal stash for template rendering — populated by {@link Mailable.template}
|
|
13
|
+
* and consumed in {@link Mailable.send} after `build()` resolves.
|
|
14
|
+
*/
|
|
15
|
+
declare interface TemplateRef {
|
|
16
|
+
name: string
|
|
17
|
+
props: Record<string, unknown>
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Allowed recipient input — accepts a single address, an array of addresses,
|
|
21
|
+
* or already-shaped {@link EmailAddress} objects. Strings are treated as
|
|
22
|
+
* `address` only (no display name).
|
|
23
|
+
*/
|
|
24
|
+
export type MailableAddressInput = string | string[] | EmailAddress | EmailAddress[];
|
|
25
|
+
/**
|
|
26
|
+
* Laravel-style class-based email definition. Subclass `Mailable`,
|
|
27
|
+
* implement `build()`, and call `.send()` to dispatch.
|
|
28
|
+
*
|
|
29
|
+
* Compared to the existing function-form `Email` / direct `mail.send()`
|
|
30
|
+
* APIs this gives you:
|
|
31
|
+
* - encapsulation of recipient/subject/body building per email type
|
|
32
|
+
* - chainable, immutable-feeling fluent setters
|
|
33
|
+
* - a single hook (`build`) where view-model -> message translation happens
|
|
34
|
+
* - automatic STX template rendering via the existing `template()` helper
|
|
35
|
+
*
|
|
36
|
+
* The class still ultimately routes through the same `mail` singleton, so
|
|
37
|
+
* configured drivers, queueing, and `from` defaults all behave identically.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* import { Mailable } from '@stacksjs/email'
|
|
42
|
+
*
|
|
43
|
+
* export default class WelcomeMail extends Mailable {
|
|
44
|
+
* constructor(private user: { name: string, email: string }) { super() }
|
|
45
|
+
*
|
|
46
|
+
* build() {
|
|
47
|
+
* return this
|
|
48
|
+
* .to(this.user.email)
|
|
49
|
+
* .subject('Welcome!')
|
|
50
|
+
* .template('welcome', { name: this.user.name })
|
|
51
|
+
* }
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* await new WelcomeMail(user).send()
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare abstract class Mailable {
|
|
58
|
+
protected _to: string[] | EmailAddress[];
|
|
59
|
+
protected _cc: string[] | EmailAddress[];
|
|
60
|
+
protected _bcc: string[] | EmailAddress[];
|
|
61
|
+
protected _replyTo?: EmailAddress;
|
|
62
|
+
protected _from?: EmailAddress;
|
|
63
|
+
protected _subject?: string;
|
|
64
|
+
protected _text?: string;
|
|
65
|
+
protected _html?: string;
|
|
66
|
+
protected _template?: TemplateRef;
|
|
67
|
+
protected _attachments: EmailAttachment[];
|
|
68
|
+
abstract build(): this | Promise<this>;
|
|
69
|
+
to(address: MailableAddressInput): this;
|
|
70
|
+
cc(address: MailableAddressInput): this;
|
|
71
|
+
bcc(address: MailableAddressInput): this;
|
|
72
|
+
replyTo(address: string | EmailAddress): this;
|
|
73
|
+
from(addr: EmailAddress): this;
|
|
74
|
+
subject(s: string): this;
|
|
75
|
+
text(body: string): this;
|
|
76
|
+
html(body: string): this;
|
|
77
|
+
template(name: string, props?: Record<string, unknown>): this;
|
|
78
|
+
attach(path: string, name?: string): this;
|
|
79
|
+
attachData(buffer: Uint8Array | string, name: string, mime?: string): this;
|
|
80
|
+
send(options?: MailableSendOptions): Promise<EmailResult>;
|
|
81
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render an email template with optional layout
|
|
3
|
+
*
|
|
4
|
+
* Supports both .stx and .html templates. When a .stx template
|
|
5
|
+
* is found, it uses the STX engine for rendering (with directives,
|
|
6
|
+
* server scripts, etc.). When an .html template is found, it uses
|
|
7
|
+
* simple {{ variable }} replacement with layout wrapping.
|
|
8
|
+
*
|
|
9
|
+
* .stx templates are preferred over .html when both exist.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // STX template (resources/emails/welcome.stx)
|
|
14
|
+
* const { html, text } = await template('welcome', {
|
|
15
|
+
* variables: { userName: 'John' }
|
|
16
|
+
* })
|
|
17
|
+
*
|
|
18
|
+
* // HTML template with layout
|
|
19
|
+
* const { html, text } = await template('notification', {
|
|
20
|
+
* layout: 'base',
|
|
21
|
+
* variables: { message: 'Hello' }
|
|
22
|
+
* })
|
|
23
|
+
*
|
|
24
|
+
* // Without layout (HTML templates only)
|
|
25
|
+
* const { html, text } = await template('simple', {
|
|
26
|
+
* layout: false
|
|
27
|
+
* })
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function template(templateName: string, options?: TemplateOptions): Promise<TemplateResult>;
|
|
31
|
+
/**
|
|
32
|
+
* Render a raw HTML string with variables (no file loading)
|
|
33
|
+
*/
|
|
34
|
+
export declare function renderHtml(htmlContent: string, variables?: TemplateVariables): TemplateResult;
|
|
35
|
+
/**
|
|
36
|
+
* Check if a template exists (.stx or .html)
|
|
37
|
+
*/
|
|
38
|
+
export declare function templateExists(templateName: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* List available templates (.stx and .html)
|
|
41
|
+
*/
|
|
42
|
+
export declare function listTemplates(): string[];
|
|
43
|
+
export declare interface TemplateResult {
|
|
44
|
+
html: string
|
|
45
|
+
text: string
|
|
46
|
+
}
|
|
47
|
+
export declare interface TemplateOptions {
|
|
48
|
+
variables?: TemplateVariables
|
|
49
|
+
layout?: string | false
|
|
50
|
+
subject?: string
|
|
51
|
+
}
|
|
52
|
+
/** Allowed types for email template variable values */
|
|
53
|
+
export type TemplateVariableValue = string | number | boolean | undefined | null;
|
|
54
|
+
/** Map of variable names to their values for template replacement */
|
|
55
|
+
export type TemplateVariables = Record<string, TemplateVariableValue>;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export declare interface Message {
|
|
2
2
|
name: string
|
|
3
3
|
subject: string
|
|
4
|
-
to: string
|
|
4
|
+
to: string | string[]
|
|
5
5
|
from?: {
|
|
6
6
|
name: string
|
|
7
7
|
address: string
|
|
8
8
|
}
|
|
9
9
|
template: string
|
|
10
|
-
handle?: () => Promise<{ message: string }>
|
|
10
|
+
handle?: () => Promise<{ message: string }>
|
|
11
11
|
onError?: (error: Error) => Promise<{ message: string }>
|
|
12
12
|
onSuccess?: () => void
|
|
13
13
|
}
|
|
@@ -34,4 +34,4 @@ export declare interface EmailParams {
|
|
|
34
34
|
from: string
|
|
35
35
|
subject: string
|
|
36
36
|
html: string
|
|
37
|
-
}
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/email",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.70.
|
|
4
|
+
"version": "0.70.26",
|
|
5
5
|
"description": "The Stacks Email integration. Painlessly create & manage your inboxes, templates, and send emails.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
|
-
"contributors": [
|
|
7
|
+
"contributors": [
|
|
8
|
+
"Chris Breuer <chris@stacksjs.com>"
|
|
9
|
+
],
|
|
8
10
|
"license": "MIT",
|
|
9
11
|
"funding": "https://github.com/sponsors/chrisbbreuer",
|
|
10
12
|
"homepage": "https://github.com/stacksjs/stacks/tree/main/storage/framework/core/email#readme",
|
|
@@ -16,31 +18,45 @@
|
|
|
16
18
|
"bugs": {
|
|
17
19
|
"url": "https://github.com/stacksjs/stacks/issues"
|
|
18
20
|
},
|
|
19
|
-
"keywords": [
|
|
21
|
+
"keywords": [
|
|
22
|
+
"email",
|
|
23
|
+
"ses",
|
|
24
|
+
"aws",
|
|
25
|
+
"stacks",
|
|
26
|
+
"emailjs",
|
|
27
|
+
"mailgun"
|
|
28
|
+
],
|
|
20
29
|
"exports": {
|
|
21
30
|
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"bun": "./src/index.ts",
|
|
22
33
|
"import": "./dist/index.js"
|
|
23
34
|
},
|
|
24
35
|
"./*": {
|
|
36
|
+
"bun": "./src/*",
|
|
25
37
|
"import": "./dist/*"
|
|
26
38
|
}
|
|
27
39
|
},
|
|
28
40
|
"module": "dist/index.js",
|
|
29
41
|
"types": "dist/index.d.ts",
|
|
30
|
-
"files": [
|
|
42
|
+
"files": [
|
|
43
|
+
"README.md",
|
|
44
|
+
"dist"
|
|
45
|
+
],
|
|
31
46
|
"scripts": {
|
|
32
47
|
"build": "bun build.ts",
|
|
33
48
|
"build:inbound": "bun build-inbound.ts",
|
|
34
49
|
"typecheck": "bun tsc --noEmit",
|
|
35
50
|
"prepublishOnly": "bun run build"
|
|
36
51
|
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@stacksjs/ts-cloud": "^0.2.15"
|
|
54
|
+
},
|
|
37
55
|
"devDependencies": {
|
|
38
|
-
"@stacksjs/cli": "0.70.
|
|
39
|
-
"@stacksjs/config": "0.70.
|
|
40
|
-
"
|
|
41
|
-
"@stacksjs/error-handling": "0.70.
|
|
42
|
-
"@stacksjs/types": "0.70.
|
|
43
|
-
"aws-sdk": "^2.1692.0",
|
|
44
|
-
"vue-email": "^0.8.11"
|
|
56
|
+
"@stacksjs/cli": "0.70.23",
|
|
57
|
+
"@stacksjs/config": "0.70.23",
|
|
58
|
+
"better-dx": "^0.2.12",
|
|
59
|
+
"@stacksjs/error-handling": "0.70.23",
|
|
60
|
+
"@stacksjs/types": "0.70.23"
|
|
45
61
|
}
|
|
46
62
|
}
|
package/dist/base.d.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import type { EmailDriver, EmailDriverConfig, EmailMessage, EmailResult, RenderOptions } from '@stacksjs/types';
|
|
2
|
-
|
|
3
|
-
export declare abstract class BaseEmailDriver implements EmailDriver {
|
|
4
|
-
public abstract name: string
|
|
5
|
-
protected config: Required<EmailDriverConfig>
|
|
6
|
-
|
|
7
|
-
constructor(config?: EmailDriverConfig) {
|
|
8
|
-
this.config = {
|
|
9
|
-
maxRetries: config?.maxRetries || 3,
|
|
10
|
-
retryTimeout: config?.retryTimeout || 1000,
|
|
11
|
-
...config,
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
public configure(config: EmailDriverConfig): void {
|
|
16
|
-
this.config = { ...this.config, ...config }
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
public abstract send(message: EmailMessage, options?: RenderOptions): Promise<EmailResult>
|
|
20
|
-
|
|
21
|
-
protected validateMessage(message: EmailMessage): boolean {
|
|
22
|
-
if (!message.from?.address) {
|
|
23
|
-
throw new Error('Email sender address is required')
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (!message.to || (Array.isArray(message.to) && message.to.length === 0)) {
|
|
27
|
-
throw new Error('At least one recipient is required')
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (!message.subject) {
|
|
31
|
-
throw new Error('Email subject is required')
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return true
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
protected formatAddresses(addresses: string | string[] | EmailAddress[] | undefined): string[] {
|
|
38
|
-
if (!addresses)
|
|
39
|
-
return []
|
|
40
|
-
|
|
41
|
-
if (typeof addresses === 'string') {
|
|
42
|
-
return [addresses]
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return addresses.map((addr) => {
|
|
46
|
-
if (typeof addr === 'string')
|
|
47
|
-
return addr
|
|
48
|
-
return addr.name ? `${addr.name} <${addr.address}>` : addr.address
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
protected async handleError(error: unknown, message: EmailMessage): Promise<EmailResult> {
|
|
53
|
-
const err = error instanceof Error ? error : new Error(String(error))
|
|
54
|
-
|
|
55
|
-
log.error(`[${this.name}] Email sending failed`, {
|
|
56
|
-
error: err.message,
|
|
57
|
-
stack: err.stack,
|
|
58
|
-
to: message.to,
|
|
59
|
-
subject: message.subject,
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
let result: EmailResult = {
|
|
63
|
-
message: `Email sending failed: ${err.message}`,
|
|
64
|
-
success: false,
|
|
65
|
-
provider: this.name,
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (message.onError) {
|
|
69
|
-
const customResult = message.onError(err)
|
|
70
|
-
const handlerResult = customResult instanceof Promise
|
|
71
|
-
? await customResult
|
|
72
|
-
: customResult
|
|
73
|
-
|
|
74
|
-
result = {
|
|
75
|
-
...result,
|
|
76
|
-
...handlerResult,
|
|
77
|
-
success: false,
|
|
78
|
-
provider: this.name,
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return result
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
protected async handleSuccess(message: EmailMessage, messageId?: string): Promise<EmailResult> {
|
|
86
|
-
let result: EmailResult = {
|
|
87
|
-
message: 'Email sent successfully',
|
|
88
|
-
success: true,
|
|
89
|
-
provider: this.name,
|
|
90
|
-
messageId,
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
if (message.handle) {
|
|
95
|
-
const customResult = message.handle()
|
|
96
|
-
const handlerResult = customResult instanceof Promise
|
|
97
|
-
? await customResult
|
|
98
|
-
: customResult
|
|
99
|
-
|
|
100
|
-
result = {
|
|
101
|
-
...result,
|
|
102
|
-
...handlerResult,
|
|
103
|
-
success: true,
|
|
104
|
-
provider: this.name,
|
|
105
|
-
messageId,
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (message.onSuccess) {
|
|
110
|
-
const successResult = message.onSuccess()
|
|
111
|
-
const handlerResult = successResult instanceof Promise
|
|
112
|
-
? await successResult
|
|
113
|
-
: successResult
|
|
114
|
-
|
|
115
|
-
result = {
|
|
116
|
-
...result,
|
|
117
|
-
...handlerResult,
|
|
118
|
-
success: true,
|
|
119
|
-
provider: this.name,
|
|
120
|
-
messageId,
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
catch (error) {
|
|
125
|
-
return this.handleError(error, message)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return result
|
|
129
|
-
}
|
|
130
|
-
}
|
package/dist/config.d.ts
DELETED
package/dist/email.d.ts
DELETED
package/dist/index.d.ts
DELETED
package/dist/mailgun.d.ts
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import type { EmailAddress, EmailMessage, EmailResult, RenderOptions } from '@stacksjs/types';
|
|
2
|
-
|
|
3
|
-
export declare class MailgunDriver extends BaseEmailDriver {
|
|
4
|
-
public name = 'mailgun'
|
|
5
|
-
private apiKey: string
|
|
6
|
-
private domain: string
|
|
7
|
-
private endpoint: string
|
|
8
|
-
|
|
9
|
-
constructor() {
|
|
10
|
-
super()
|
|
11
|
-
this.apiKey = config.services.mailgun?.apiKey ?? ''
|
|
12
|
-
this.domain = config.services.mailgun?.domain ?? ''
|
|
13
|
-
this.endpoint = config.services.mailgun?.endpoint ?? 'api.mailgun.net'
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
public async send(message: EmailMessage, options?: RenderOptions): Promise<EmailResult> {
|
|
17
|
-
const logContext = {
|
|
18
|
-
provider: this.name,
|
|
19
|
-
to: message.to,
|
|
20
|
-
subject: message.subject,
|
|
21
|
-
domain: this.domain,
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
log.info('Sending email via Mailgun...', logContext)
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
this.validateMessage(message)
|
|
28
|
-
|
|
29
|
-
let htmlContent: string | undefined
|
|
30
|
-
if (message.template) {
|
|
31
|
-
const templ = await template(message.template, options)
|
|
32
|
-
if (templ && 'html' in templ) {
|
|
33
|
-
htmlContent = templ.html
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const formData = new FormData()
|
|
38
|
-
formData.append('from', this.formatMailgunAddress(message.from))
|
|
39
|
-
|
|
40
|
-
this.formatMailgunAddresses(message.to).forEach(to => formData.append('to', to))
|
|
41
|
-
|
|
42
|
-
if (message.cc)
|
|
43
|
-
this.formatMailgunAddresses(message.cc).forEach(cc => formData.append('cc', cc))
|
|
44
|
-
|
|
45
|
-
if (message.bcc)
|
|
46
|
-
this.formatMailgunAddresses(message.bcc).forEach(bcc => formData.append('bcc', bcc))
|
|
47
|
-
|
|
48
|
-
formData.append('subject', message.subject)
|
|
49
|
-
|
|
50
|
-
if (htmlContent) {
|
|
51
|
-
formData.append('html', htmlContent)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (message.text)
|
|
55
|
-
formData.append('text', message.text)
|
|
56
|
-
|
|
57
|
-
if (message.attachments) {
|
|
58
|
-
message.attachments.forEach((attachment) => {
|
|
59
|
-
const content = typeof attachment.content === 'string'
|
|
60
|
-
? attachment.content
|
|
61
|
-
: this.arrayBufferToBase64(attachment.content)
|
|
62
|
-
|
|
63
|
-
formData.append('attachment', new Blob([content], { type: attachment.contentType }), attachment.filename)
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const response = await this.sendWithRetry(formData)
|
|
68
|
-
return this.handleSuccess(message, response.id)
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
return this.handleError(error, message)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
private formatMailgunAddress(address: EmailAddress): string {
|
|
76
|
-
return address.name ? `${address.name} <${address.address}>` : address.address
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
private formatMailgunAddresses(addresses: string | string[] | EmailAddress[] | undefined): string[] {
|
|
80
|
-
if (!addresses)
|
|
81
|
-
return []
|
|
82
|
-
|
|
83
|
-
if (typeof addresses === 'string')
|
|
84
|
-
return [addresses]
|
|
85
|
-
|
|
86
|
-
return addresses.map((addr) => {
|
|
87
|
-
if (typeof addr === 'string')
|
|
88
|
-
return addr
|
|
89
|
-
return addr.name ? `${addr.name} <${addr.address}>` : addr.address
|
|
90
|
-
})
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
private arrayBufferToBase64(buffer: Uint8Array): string {
|
|
94
|
-
let binary = ''
|
|
95
|
-
const bytes = new Uint8Array(buffer)
|
|
96
|
-
const len = bytes.byteLength
|
|
97
|
-
|
|
98
|
-
for (let i = 0; i < len; i++) {
|
|
99
|
-
binary += String.fromCharCode(bytes[i])
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return typeof btoa === 'function'
|
|
103
|
-
? btoa(binary)
|
|
104
|
-
: Buffer.from(binary).toString('base64')
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private async sendWithRetry(formData: FormData, attempt = 1): Promise<any> {
|
|
108
|
-
const url = `https:
|
|
109
|
-
const auth = Buffer.from(`api:${this.apiKey}`).toString('base64')
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
const response = await fetch(url, {
|
|
113
|
-
method: 'POST',
|
|
114
|
-
headers: {
|
|
115
|
-
Authorization: `Basic ${auth}`,
|
|
116
|
-
},
|
|
117
|
-
body: formData,
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
if (!response.ok) {
|
|
121
|
-
const errorData = await response.json()
|
|
122
|
-
throw new Error(`Mailgun API error: ${response.status} - ${JSON.stringify(errorData)}`)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const data = await response.json()
|
|
126
|
-
log.info(`[${this.name}] Email sent successfully`, { attempt, messageId: data.id })
|
|
127
|
-
return data
|
|
128
|
-
}
|
|
129
|
-
catch (error) {
|
|
130
|
-
if (attempt < (config.services.mailgun?.maxRetries ?? 3)) {
|
|
131
|
-
const retryTimeout = config.services.mailgun?.retryTimeout ?? 1000
|
|
132
|
-
log.warn(`[${this.name}] Email send failed, retrying (${attempt}/${config.services.mailgun?.maxRetries ?? 3})`)
|
|
133
|
-
await new Promise(resolve => setTimeout(resolve, retryTimeout))
|
|
134
|
-
return this.sendWithRetry(formData, attempt + 1)
|
|
135
|
-
}
|
|
136
|
-
throw error
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export default MailgunDriver;
|