@stacksjs/email 0.70.355 → 0.70.356

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/email.d.ts CHANGED
@@ -14,6 +14,10 @@ declare interface EmailFromAddress {
14
14
  declare interface MailConfig {
15
15
  defaultDriver?: string
16
16
  }
17
+ export declare class EmailDeliveryError extends Error {
18
+ readonly result: EmailResult;
19
+ constructor(result: EmailResult);
20
+ }
17
21
  /**
18
22
  * Email notification class for defining email notifications
19
23
  */
@@ -32,6 +36,7 @@ export declare class Email {
32
36
  export declare class Mail {
33
37
  constructor(options?: MailConfig);
34
38
  send(message: EmailMessage): Promise<EmailResult>;
39
+ sendOrFail(message: EmailMessage): Promise<EmailResult>;
35
40
  use(driver: string): Mail;
36
41
  queue(message: EmailMessage): Promise<void>;
37
42
  later(delaySeconds: number, message: EmailMessage): Promise<void>;
package/dist/email.js CHANGED
@@ -1 +1 @@
1
- import{config}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{CaptureEmailDriver}from"./drivers/capture";import{LogEmailDriver}from"./drivers/log";import{MailgunDriver}from"./drivers/mailgun";import{MailtrapDriver}from"./drivers/mailtrap";import{SendGridDriver}from"./drivers/sendgrid";import{SESDriver}from"./drivers/ses";import{SMTPDriver}from"./drivers/smtp";import{findEmailByIdempotencyKey,recordEmailIdempotency}from"./idempotency";import{checkSuppressionFor}from"./suppression";export class Email{name;subject;to;from;template;handle;onError;onSuccess;constructor(options){this.name=options.name;this.subject=options.subject;this.to=options.to;this.from=options.from;this.template=options.template;this.handle=options.handle;this.onError=options.onError;this.onSuccess=options.onSuccess}async renderTemplate(){if(!this.template)return"";try{const{path:p}=await import("@stacksjs/path"),templatePath=p.resourcesPath(`views/emails/${this.template}.html`),file=Bun.file(templatePath);if(await file.exists())return await file.text()}catch{}if(this.template.includes("<"))return this.template;return`<p>${this.template}</p>`}async send(to){const target=to??this.to,recipients=Array.isArray(target)?target:target?[target]:[];if(recipients.length===0)throw Error("No recipient specified for email");try{await mail.send({to:recipients,from:this.from||{name:config.email.from?.name||"Stacks",address:config.email.from?.address||"no-reply@stacksjs.com"},subject:this.subject,html:await this.renderTemplate()});if(this.onSuccess)this.onSuccess();if(this.handle)return this.handle();return{message:"Email sent"}}catch(error){if(this.onError)return this.onError(error instanceof Error?error:Error(String(error)));throw error}}}export class Mail{drivers=new Map;defaultDriver;constructor(options={}){this.defaultDriver=options.defaultDriver||config.email.default||"ses";this.registerDefaultDrivers()}registerDefaultDrivers(){this.drivers.set("log",new LogEmailDriver);this.drivers.set("ses",new SESDriver);this.drivers.set("sendgrid",new SendGridDriver);this.drivers.set("mailgun",new MailgunDriver);this.drivers.set("mailtrap",new MailtrapDriver);this.drivers.set("smtp",new SMTPDriver);this.drivers.set("capture",new CaptureEmailDriver)}async send(message){const driver=this.drivers.get(this.defaultDriver);if(!driver){const available=[...this.drivers.keys()].sort().join(", ");throw Error(`Email driver '${this.defaultDriver}' is not registered. Available drivers: [${available}]. Check config.email.default or the MAIL_MAILER environment variable.`)}if(message.idempotencyKey){const cached=await findEmailByIdempotencyKey(message.idempotencyKey);if(cached)return cached}const suppressionType=await checkSuppressionForFirstRecipient(message);if(suppressionType)return{success:!1,message:`suppressed:${suppressionType}`,provider:"suppression"};const defaultFrom={name:config.email.from?.name||"Stacks",address:config.email.from?.address||"no-reply@stacksjs.com"},result=await driver.send({...message,from:message.from||defaultFrom});if(message.idempotencyKey)await recordEmailIdempotency(message.idempotencyKey,message,result);return result}use(driver){if(!this.drivers.has(driver))throw Error(`Email driver '${driver}' is not available`);return new Mail({defaultDriver:driver})}async queue(message){await this.dispatchOrFallback(message,async()=>{const{job}=await import("@stacksjs/queue");await job("SendEmail",{message,driver:this.defaultDriver}).onQueue("emails").dispatch()},{context:"queue"})}async later(delaySeconds,message){await this.dispatchOrFallback(message,async()=>{const{job}=await import("@stacksjs/queue");await job("SendEmail",{message,driver:this.defaultDriver}).onQueue("emails").delay(delaySeconds).dispatch()},{context:"later",delaySeconds})}async queueOn(queueName,message){await this.dispatchOrFallback(message,async()=>{const{job}=await import("@stacksjs/queue");await job("SendEmail",{message,driver:this.defaultDriver}).onQueue(queueName).dispatch()},{context:"queueOn",queueName})}async dispatchOrFallback(message,dispatch,logExtra){try{await dispatch();return}catch(error){const reason=error instanceof Error?error.message:String(error);log.warn("[email] Queue dispatch failed; falling back to synchronous send. "+"Background email pipeline is degraded \u2014 check the queue worker / broker.",{...logExtra,reason})}await this.send(message)}}async function checkSuppressionForFirstRecipient(message){const recipients=collectRecipientAddresses(message);if(recipients.length===0)return null;for(const addr of recipients){const matched=await checkSuppressionFor(addr,message.tag);if(matched)return matched}return null}function collectRecipientAddresses(message){const out=[],pushOne=(v)=>{if(typeof v==="string"){out.push(v);return}if(v&&typeof v==="object"&&"address"in v&&typeof v.address==="string")out.push(v.address)};for(const field of["to","cc","bcc"]){const v=message[field];if(!v)continue;if(Array.isArray(v))for(const item of v)pushOne(item);else pushOne(v)}return out}let _mail;function getMail(){if(!_mail){const driver=config?.email?.default||process.env.MAIL_MAILER||"ses";_mail=new Mail({defaultDriver:driver})}return _mail}export const mail=new Proxy({},{get(_t,prop){return getMail()[prop]},set(_t,prop,value){getMail()[prop]=value;return!0}});
1
+ import{config}from"@stacksjs/config";import{log}from"@stacksjs/logging";import{CaptureEmailDriver}from"./drivers/capture";import{LogEmailDriver}from"./drivers/log";import{MailgunDriver}from"./drivers/mailgun";import{MailtrapDriver}from"./drivers/mailtrap";import{SendGridDriver}from"./drivers/sendgrid";import{SESDriver}from"./drivers/ses";import{SMTPDriver}from"./drivers/smtp";import{findEmailByIdempotencyKey,recordEmailIdempotency}from"./idempotency";import{checkSuppressionFor}from"./suppression";export class EmailDeliveryError extends Error{result;constructor(result){super(result.message||`Email delivery failed through ${result.provider||"the configured provider"}.`);this.name="EmailDeliveryError";this.result=result}}export class Email{name;subject;to;from;template;handle;onError;onSuccess;constructor(options){this.name=options.name;this.subject=options.subject;this.to=options.to;this.from=options.from;this.template=options.template;this.handle=options.handle;this.onError=options.onError;this.onSuccess=options.onSuccess}async renderTemplate(){if(!this.template)return"";try{const{path:p}=await import("@stacksjs/path"),templatePath=p.resourcesPath(`views/emails/${this.template}.html`),file=Bun.file(templatePath);if(await file.exists())return await file.text()}catch{}if(this.template.includes("<"))return this.template;return`<p>${this.template}</p>`}async send(to){const target=to??this.to,recipients=Array.isArray(target)?target:target?[target]:[];if(recipients.length===0)throw Error("No recipient specified for email");try{await mail.sendOrFail({to:recipients,from:this.from||{name:config.email.from?.name||"Stacks",address:config.email.from?.address||"no-reply@stacksjs.com"},subject:this.subject,html:await this.renderTemplate()});if(this.onSuccess)this.onSuccess();if(this.handle)return this.handle();return{message:"Email sent"}}catch(error){if(this.onError)return this.onError(error instanceof Error?error:Error(String(error)));throw error}}}export class Mail{drivers=new Map;defaultDriver;constructor(options={}){this.defaultDriver=options.defaultDriver||config.email.default||"ses";this.registerDefaultDrivers()}registerDefaultDrivers(){this.drivers.set("log",new LogEmailDriver);this.drivers.set("ses",new SESDriver);this.drivers.set("sendgrid",new SendGridDriver);this.drivers.set("mailgun",new MailgunDriver);this.drivers.set("mailtrap",new MailtrapDriver);this.drivers.set("smtp",new SMTPDriver);this.drivers.set("capture",new CaptureEmailDriver)}async send(message){const driver=this.drivers.get(this.defaultDriver);if(!driver){const available=[...this.drivers.keys()].sort().join(", ");throw Error(`Email driver '${this.defaultDriver}' is not registered. Available drivers: [${available}]. Check config.email.default or the MAIL_MAILER environment variable.`)}if(message.idempotencyKey){const cached=await findEmailByIdempotencyKey(message.idempotencyKey);if(cached)return cached}const suppressionType=await checkSuppressionForFirstRecipient(message);if(suppressionType)return{success:!1,message:`suppressed:${suppressionType}`,provider:"suppression"};const defaultFrom={name:config.email.from?.name||"Stacks",address:config.email.from?.address||"no-reply@stacksjs.com"},result=await driver.send({...message,from:message.from||defaultFrom});if(message.idempotencyKey)await recordEmailIdempotency(message.idempotencyKey,message,result);return result}async sendOrFail(message){const result=await this.send(message);if(!result.success)throw new EmailDeliveryError(result);return result}use(driver){if(!this.drivers.has(driver))throw Error(`Email driver '${driver}' is not available`);return new Mail({defaultDriver:driver})}async queue(message){await this.dispatchOrFallback(message,async()=>{const{job}=await import("@stacksjs/queue");await job("SendEmail",{message,driver:this.defaultDriver}).onQueue("emails").dispatch()},{context:"queue"})}async later(delaySeconds,message){await this.dispatchOrFallback(message,async()=>{const{job}=await import("@stacksjs/queue");await job("SendEmail",{message,driver:this.defaultDriver}).onQueue("emails").delay(delaySeconds).dispatch()},{context:"later",delaySeconds})}async queueOn(queueName,message){await this.dispatchOrFallback(message,async()=>{const{job}=await import("@stacksjs/queue");await job("SendEmail",{message,driver:this.defaultDriver}).onQueue(queueName).dispatch()},{context:"queueOn",queueName})}async dispatchOrFallback(message,dispatch,logExtra){try{await dispatch();return}catch(error){const reason=error instanceof Error?error.message:String(error);log.warn("[email] Queue dispatch failed; falling back to synchronous send. "+"Background email pipeline is degraded \u2014 check the queue worker / broker.",{...logExtra,reason})}await this.send(message)}}async function checkSuppressionForFirstRecipient(message){const recipients=collectRecipientAddresses(message);if(recipients.length===0)return null;for(const addr of recipients){const matched=await checkSuppressionFor(addr,message.tag);if(matched)return matched}return null}function collectRecipientAddresses(message){const out=[],pushOne=(v)=>{if(typeof v==="string"){out.push(v);return}if(v&&typeof v==="object"&&"address"in v&&typeof v.address==="string")out.push(v.address)};for(const field of["to","cc","bcc"]){const v=message[field];if(!v)continue;if(Array.isArray(v))for(const item of v)pushOne(item);else pushOne(v)}return out}let _mail;function getMail(){if(!_mail){const driver=config?.email?.default||process.env.MAIL_MAILER||"ses";_mail=new Mail({defaultDriver:driver})}return _mail}export const mail=new Proxy({},{get(_t,prop){return getMail()[prop]},set(_t,prop,value){getMail()[prop]=value;return!0}});
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/email",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.355",
5
+ "version": "0.70.356",
6
6
  "description": "The Stacks Email integration. Painlessly create & manage your inboxes, templates, and send emails.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -64,10 +64,10 @@
64
64
  "postal-mime": "^2.7.6"
65
65
  },
66
66
  "devDependencies": {
67
- "@stacksjs/cli": "0.70.355",
68
- "@stacksjs/config": "0.70.355",
67
+ "@stacksjs/cli": "0.70.356",
68
+ "@stacksjs/config": "0.70.356",
69
69
  "better-dx": "^0.2.17",
70
- "@stacksjs/error-handling": "0.70.355",
71
- "@stacksjs/types": "0.70.355"
70
+ "@stacksjs/error-handling": "0.70.356",
71
+ "@stacksjs/types": "0.70.356"
72
72
  }
73
73
  }