@stacksjs/email 0.75.72 → 0.75.73
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/dist/drivers/mailtrap.d.ts +20 -0
- package/dist/drivers/mailtrap.js +1 -1
- package/package.json +15 -15
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
import { BaseEmailDriver } from './base';
|
|
2
2
|
import type { EmailMessage, EmailResult } from '@stacksjs/types';
|
|
3
3
|
import type { TemplateOptions } from '../template';
|
|
4
|
+
/**
|
|
5
|
+
* Where a send goes.
|
|
6
|
+
*
|
|
7
|
+
* Mailtrap has two APIs that differ in exactly this: the sandbox captures
|
|
8
|
+
* mail into a testing inbox and needs that inbox's id in the path, the
|
|
9
|
+
* Sending API delivers it and takes no inbox at all. The driver used to
|
|
10
|
+
* default to the sandbox and require an inbox id, so an app that configured
|
|
11
|
+
* Mailtrap for production could not send through it, and one that left
|
|
12
|
+
* `MAILTRAP_HOST` unset did not get the default either: `config/services.ts`
|
|
13
|
+
* sets it to `''`, which `??` keeps, and the request went to `/<inbox id>`.
|
|
14
|
+
*
|
|
15
|
+
* So the inbox id decides: with one, the sandbox inbox; without one, real
|
|
16
|
+
* delivery. `MAILTRAP_HOST` still overrides the host either way (a bulk
|
|
17
|
+
* stream, a proxy).
|
|
18
|
+
*/
|
|
19
|
+
export declare function mailtrapEndpoint(options: { host?: unknown, inboxId?: unknown }): string;
|
|
20
|
+
/** Mailtrap's Email Sending API: real delivery, no inbox in the path. */
|
|
21
|
+
export declare const MAILTRAP_SENDING_URL: 'https://send.api.mailtrap.io/api/send';
|
|
22
|
+
/** Mailtrap's Email Testing (sandbox) API: captures into an inbox, delivers nothing. */
|
|
23
|
+
export declare const MAILTRAP_SANDBOX_URL: 'https://sandbox.api.mailtrap.io/api/send';
|
|
4
24
|
export declare class MailtrapDriver extends BaseEmailDriver {
|
|
5
25
|
name: string;
|
|
6
26
|
send(message: EmailMessage, options?: TemplateOptions): Promise<EmailResult>;
|
package/dist/drivers/mailtrap.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Buffer}from"node:buffer";import{config}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{templateByName}from"../template";import{filterStringHeaders}from"../validation";import{BaseEmailDriver}from"./base";export
|
|
1
|
+
import{Buffer}from"node:buffer";import{config}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{templateByName}from"../template";import{filterStringHeaders}from"../validation";import{BaseEmailDriver}from"./base";export const MAILTRAP_SENDING_URL="https://send.api.mailtrap.io/api/send",MAILTRAP_SANDBOX_URL="https://sandbox.api.mailtrap.io/api/send";function present(value){const text=value==null?"":String(value).trim();return text?text:void 0}export function mailtrapEndpoint(options){const host=present(options.host)?.replace(/\/+$/,""),inboxId=present(options.inboxId);if(inboxId)return`${host??MAILTRAP_SANDBOX_URL}/${encodeURIComponent(inboxId)}`;if(host&&/sandbox\.api\.mailtrap\.io/i.test(host))throw Error("MAILTRAP_HOST points at the Mailtrap sandbox, which needs MAILTRAP_INBOX_ID. Set the inbox id to capture mail for testing, or unset MAILTRAP_HOST to deliver it.");return host??MAILTRAP_SENDING_URL}export class MailtrapDriver extends BaseEmailDriver{name="mailtrap";getConfig(){const mailtrap=config.services.mailtrap,token=present(mailtrap?.token);if(!token)throw Error("Mailtrap API token is required but not provided. Please set MAILTRAP_TOKEN in your environment variables.");return{endpoint:mailtrapEndpoint({host:mailtrap?.host,inboxId:mailtrap?.inboxId}),token,inboxId:present(mailtrap?.inboxId)}}async send(message,options){const inboxId=present(config.services.mailtrap?.inboxId),logContext={provider:this.name,to:message.to,subject:message.subject,inboxId};log.info("Sending email via Mailtrap...",logContext);try{this.validateMessage(message);let templ;if(message.template)templ=await templateByName(message.template,options);const htmlContent=templ?.html||message.html,replyTo=this.firstMailtrapAddress(message.replyTo),customHeaders=filterStringHeaders(message.headers),mailtrapPayload={from:{email:message.from?.address||config.email.from?.address||"",name:message.from?.name||config.email.from?.name},to:this.formatMailtrapAddresses(message.to),...message.cc&&{cc:this.formatMailtrapAddresses(message.cc)},...message.bcc&&{bcc:this.formatMailtrapAddresses(message.bcc)},...replyTo?{reply_to:replyTo}:{},...customHeaders?{headers:customHeaders}:{},subject:message.subject,...htmlContent&&{html:htmlContent},...message.text&&{text:message.text},...message.attachments&&{attachments:message.attachments.map((attachment)=>({filename:attachment.filename,content:typeof attachment.content==="string"?attachment.content:this.arrayBufferToBase64(attachment.content),type:attachment.contentType||"application/octet-stream"}))}},response=await this.sendWithRetry(mailtrapPayload);return this.handleSuccess(message,response.message_ids?.[0])}catch(error){return this.handleError(error,message)}}formatMailtrapAddresses(addresses){if(!addresses)return[];if(typeof addresses==="string")return[{email:addresses}];return addresses.map((addr)=>{if(typeof addr==="string")return{email:addr};return{email:addr.address,...addr.name&&{name:addr.name}}})}firstMailtrapAddress(value){if(!value)return;if(typeof value==="string")return{email:value};if(Array.isArray(value)){const first=value[0];if(first===void 0)return;if(typeof first==="string")return{email:first};return{email:first.address,...first.name&&{name:first.name}}}return{email:value.address,...value.name&&{name:value.name}}}arrayBufferToBase64(buffer){let binary="";const bytes=new Uint8Array(buffer),len=bytes.byteLength;for(let i=0;i<len;i++)binary+=String.fromCharCode(bytes[i]??0);return typeof btoa==="function"?btoa(binary):Buffer.from(binary).toString("base64")}async sendWithRetry(payload,attempt=1){const{endpoint,token}=this.getConfig();try{const response=await fetch(endpoint,{method:"POST",headers:{Authorization:`Bearer ${token}`,"Content-Type":"application/json"},body:JSON.stringify(payload)});if(!response.ok){const errorData=await response.json().catch(()=>({})),err=Error(`Mailtrap API error: ${response.status} - ${JSON.stringify(errorData)}`);err.status=response.status;throw err}const data=await response.json();log.info(`[${this.name}] Email sent successfully`,{attempt,messageId:data.message_ids?.[0]});return data}catch(error){const status=error?.status,nonRetryable=typeof status==="number"&&status>=400&&status<500&&status!==429,maxRetries=config.services.mailtrap?.maxRetries??3;if(!nonRetryable&&attempt<maxRetries){const retryTimeout=config.services.mailtrap?.retryTimeout??1000;log.warn(`[${this.name}] Email send failed, retrying (${attempt}/${maxRetries})`);await new Promise((resolve)=>setTimeout(resolve,retryTimeout));return this.sendWithRetry(payload,attempt+1)}throw error}}}export default MailtrapDriver;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/email",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.73",
|
|
6
6
|
"description": "The Stacks Email integration. Painlessly create & manage your inboxes, templates, and send emails.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -61,26 +61,26 @@
|
|
|
61
61
|
"prepublishOnly": "bun run build"
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
|
-
"@stacksjs/config": "0.75.
|
|
65
|
-
"@stacksjs/database": "0.75.
|
|
66
|
-
"@stacksjs/events": "0.75.
|
|
67
|
-
"@stacksjs/logging": "0.75.
|
|
64
|
+
"@stacksjs/config": "0.75.73",
|
|
65
|
+
"@stacksjs/database": "0.75.73",
|
|
66
|
+
"@stacksjs/events": "0.75.73",
|
|
67
|
+
"@stacksjs/logging": "0.75.73",
|
|
68
68
|
"@stacksjs/mail": "^0.3.11",
|
|
69
|
-
"@stacksjs/path": "0.75.
|
|
70
|
-
"@stacksjs/storage": "0.75.
|
|
71
|
-
"@stacksjs/strings": "0.75.
|
|
72
|
-
"@stacksjs/stx": "^0.2.
|
|
73
|
-
"@stacksjs/ts-cloud": "^0.16.
|
|
74
|
-
"@stacksjs/utils": "0.75.
|
|
69
|
+
"@stacksjs/path": "0.75.73",
|
|
70
|
+
"@stacksjs/storage": "0.75.73",
|
|
71
|
+
"@stacksjs/strings": "0.75.73",
|
|
72
|
+
"@stacksjs/stx": "^0.2.374",
|
|
73
|
+
"@stacksjs/ts-cloud": "^0.16.39",
|
|
74
|
+
"@stacksjs/utils": "0.75.73"
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
|
-
"@stacksjs/cli": "0.75.
|
|
78
|
-
"@stacksjs/error-handling": "0.75.
|
|
79
|
-
"@stacksjs/types": "0.75.
|
|
77
|
+
"@stacksjs/cli": "0.75.73",
|
|
78
|
+
"@stacksjs/error-handling": "0.75.73",
|
|
79
|
+
"@stacksjs/types": "0.75.73",
|
|
80
80
|
"better-dx": "^0.2.24"
|
|
81
81
|
},
|
|
82
82
|
"peerDependencies": {
|
|
83
|
-
"@stacksjs/queue": "0.75.
|
|
83
|
+
"@stacksjs/queue": "0.75.73"
|
|
84
84
|
},
|
|
85
85
|
"peerDependenciesMeta": {
|
|
86
86
|
"@stacksjs/queue": {
|