@workermailer/smtp 0.1.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.
@@ -0,0 +1,62 @@
1
+ export { A as Attachment, a as AuthType, C as Credentials, E as Email, b as EmailOptions, L as LogLevel, U as User, W as WorkerMailer, c as WorkerMailerHooks, d as WorkerMailerOptions, e as encodeHeader } from './mailer-CNOSp-w6.mjs';
2
+
3
+ /**
4
+ * Base error class for WorkerMailer
5
+ */
6
+ declare class WorkerMailerError extends Error {
7
+ readonly code: string;
8
+ constructor(message: string, code: string);
9
+ }
10
+ /**
11
+ * Error thrown when email validation fails
12
+ */
13
+ declare class InvalidEmailError extends WorkerMailerError {
14
+ readonly invalidEmails: string[];
15
+ constructor(message: string, invalidEmails?: string[]);
16
+ }
17
+ /**
18
+ * Error thrown when SMTP authentication fails
19
+ */
20
+ declare class SmtpAuthError extends WorkerMailerError {
21
+ constructor(message: string);
22
+ }
23
+ /**
24
+ * Error thrown when SMTP connection fails
25
+ */
26
+ declare class SmtpConnectionError extends WorkerMailerError {
27
+ constructor(message: string);
28
+ }
29
+ /**
30
+ * Error thrown when recipient is rejected by SMTP server
31
+ */
32
+ declare class SmtpRecipientError extends WorkerMailerError {
33
+ readonly recipient: string;
34
+ constructor(message: string, recipient: string);
35
+ }
36
+ /**
37
+ * Error thrown when SMTP operation times out
38
+ */
39
+ declare class SmtpTimeoutError extends WorkerMailerError {
40
+ constructor(message: string);
41
+ }
42
+ /**
43
+ * Error thrown when email content is invalid
44
+ */
45
+ declare class InvalidContentError extends WorkerMailerError {
46
+ constructor(message: string);
47
+ }
48
+
49
+ /**
50
+ * Validates an email address format according to RFC 5322 (simplified)
51
+ * @param email - The email address to validate
52
+ * @returns true if the email format is valid
53
+ */
54
+ declare function isValidEmail(email: string): boolean;
55
+ /**
56
+ * Validates multiple email addresses and returns invalid ones
57
+ * @param emails - Array of email addresses to validate
58
+ * @returns Array of invalid email addresses (empty if all valid)
59
+ */
60
+ declare function validateEmails(emails: string[]): string[];
61
+
62
+ export { InvalidContentError, InvalidEmailError, SmtpAuthError, SmtpConnectionError, SmtpRecipientError, SmtpTimeoutError, WorkerMailerError, isValidEmail, validateEmails };
@@ -0,0 +1,62 @@
1
+ export { A as Attachment, a as AuthType, C as Credentials, E as Email, b as EmailOptions, L as LogLevel, U as User, W as WorkerMailer, c as WorkerMailerHooks, d as WorkerMailerOptions, e as encodeHeader } from './mailer-CNOSp-w6.js';
2
+
3
+ /**
4
+ * Base error class for WorkerMailer
5
+ */
6
+ declare class WorkerMailerError extends Error {
7
+ readonly code: string;
8
+ constructor(message: string, code: string);
9
+ }
10
+ /**
11
+ * Error thrown when email validation fails
12
+ */
13
+ declare class InvalidEmailError extends WorkerMailerError {
14
+ readonly invalidEmails: string[];
15
+ constructor(message: string, invalidEmails?: string[]);
16
+ }
17
+ /**
18
+ * Error thrown when SMTP authentication fails
19
+ */
20
+ declare class SmtpAuthError extends WorkerMailerError {
21
+ constructor(message: string);
22
+ }
23
+ /**
24
+ * Error thrown when SMTP connection fails
25
+ */
26
+ declare class SmtpConnectionError extends WorkerMailerError {
27
+ constructor(message: string);
28
+ }
29
+ /**
30
+ * Error thrown when recipient is rejected by SMTP server
31
+ */
32
+ declare class SmtpRecipientError extends WorkerMailerError {
33
+ readonly recipient: string;
34
+ constructor(message: string, recipient: string);
35
+ }
36
+ /**
37
+ * Error thrown when SMTP operation times out
38
+ */
39
+ declare class SmtpTimeoutError extends WorkerMailerError {
40
+ constructor(message: string);
41
+ }
42
+ /**
43
+ * Error thrown when email content is invalid
44
+ */
45
+ declare class InvalidContentError extends WorkerMailerError {
46
+ constructor(message: string);
47
+ }
48
+
49
+ /**
50
+ * Validates an email address format according to RFC 5322 (simplified)
51
+ * @param email - The email address to validate
52
+ * @returns true if the email format is valid
53
+ */
54
+ declare function isValidEmail(email: string): boolean;
55
+ /**
56
+ * Validates multiple email addresses and returns invalid ones
57
+ * @param emails - Array of email addresses to validate
58
+ * @returns Array of invalid email addresses (empty if all valid)
59
+ */
60
+ declare function validateEmails(emails: string[]): string[];
61
+
62
+ export { InvalidContentError, InvalidEmailError, SmtpAuthError, SmtpConnectionError, SmtpRecipientError, SmtpTimeoutError, WorkerMailerError, isValidEmail, validateEmails };
package/dist/index.js ADDED
@@ -0,0 +1,54 @@
1
+ "use strict";var $=Object.defineProperty;var M=Object.getOwnPropertyDescriptor;var N=Object.getOwnPropertyNames;var W=Object.prototype.hasOwnProperty;var P=(r,e)=>{for(var t in e)$(r,t,{get:e[t],enumerable:!0})},H=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of N(e))!W.call(r,s)&&s!==t&&$(r,s,{get:()=>e[s],enumerable:!(i=M(e,s))||i.enumerable});return r};var B=r=>H($({},"__esModule",{value:!0}),r);var z={};P(z,{Email:()=>C,InvalidContentError:()=>A,InvalidEmailError:()=>S,LogLevel:()=>U,SmtpAuthError:()=>d,SmtpConnectionError:()=>E,SmtpRecipientError:()=>b,SmtpTimeoutError:()=>f,WorkerMailer:()=>I,WorkerMailerError:()=>p,encodeHeader:()=>m,isValidEmail:()=>c,validateEmails:()=>F});module.exports=B(z);var O=class{values=[];resolvers=[];enqueue(e){this.resolvers.length||this.addWrapper(),this.resolvers.shift()(e)}async dequeue(){return this.values.length||this.addWrapper(),this.values.shift()}get length(){return this.values.length}clear(){this.values=[],this.resolvers=[]}addWrapper(){this.values.push(new Promise(e=>{this.resolvers.push(e)}))}};function c(r){if(!r||typeof r!="string"||!/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(r))return!1;let[t,i]=r.split("@");if(t.length>64||i.length>255||!i.includes("."))return!1;let s=i.split(".").pop();return!(!s||s.length<2)}function F(r){return r.filter(e=>!c(e))}async function L(r,e,t){return Promise.race([r,new Promise((i,s)=>setTimeout(()=>s(t),e))])}var j=new TextEncoder;function u(r){return j.encode(r)}var Y=new TextDecoder("utf-8");function D(r){return Y.decode(r)}function R(r,e=76){let t=u(r),i="",s=0,a=0;for(;a<t.length;){let l=t[a],h;if(l===10){i+=`\r
2
+ `,s=0,a++;continue}else if(l===13)if(a+1<t.length&&t[a+1]===10){i+=`\r
3
+ `,s=0,a+=2;continue}else h="=0D";if(h===void 0){let g=l===32||l===9,T=a+1>=t.length||t[a+1]===10||t[a+1]===13;l<32&&!g||l>126||l===61||g&&T?h=`=${l.toString(16).toUpperCase().padStart(2,"0")}`:h=String.fromCharCode(l)}s+h.length>e-3&&(i+=`=\r
4
+ `,s=0),i+=h,s+=h.length,a++}return i}var p=class extends Error{code;constructor(e,t){super(e),this.name="WorkerMailerError",this.code=t}},S=class extends p{invalidEmails;constructor(e,t=[]){super(e,"INVALID_EMAIL"),this.name="InvalidEmailError",this.invalidEmails=t}},d=class extends p{constructor(e){super(e,"AUTH_FAILED"),this.name="SmtpAuthError"}},E=class extends p{constructor(e){super(e,"CONNECTION_FAILED"),this.name="SmtpConnectionError"}},b=class extends p{recipient;constructor(e,t){super(e,"RECIPIENT_REJECTED"),this.name="SmtpRecipientError",this.recipient=t}},f=class extends p{constructor(e){super(e,"TIMEOUT"),this.name="SmtpTimeoutError"}},A=class extends p{constructor(e){super(e,"INVALID_CONTENT"),this.name="InvalidContentError"}};function m(r){if(!/[^\x00-\x7F]/.test(r))return r;let e=u(r),t="";for(let i of e)i>=33&&i<=126&&i!==63&&i!==61&&i!==95?t+=String.fromCharCode(i):i===32?t+="_":t+=`=${i.toString(16).toUpperCase().padStart(2,"0")}`;return`=?UTF-8?Q?${t}?=`}var C=class r{from;to;reply;cc;bcc;subject;text;html;dsnOverride;attachments;headers;setSent;setSentError;sent=new Promise((e,t)=>{this.setSent=e,this.setSentError=t});constructor(e){if(!e.text&&!e.html)throw new A("At least one of text or html must be provided");typeof e.from=="string"?this.from={email:e.from}:this.from=e.from,typeof e.reply=="string"?this.reply={email:e.reply}:this.reply=e.reply,this.to=r.toUsers(e.to),this.cc=r.toUsers(e.cc),this.bcc=r.toUsers(e.bcc),this.validateEmails(),this.subject=e.subject,this.text=e.text,this.html=e.html,this.attachments=e.attachments,this.dsnOverride=e.dsnOverride,this.headers=e.headers||{}}static toUsers(e){if(e)return typeof e=="string"?[{email:e}]:Array.isArray(e)?e.map(t=>typeof t=="string"?{email:t}:t):[e]}validateEmails(){let e=[];c(this.from.email)||e.push(this.from.email);for(let t of this.to)c(t.email)||e.push(t.email);if(this.reply&&!c(this.reply.email)&&e.push(this.reply.email),this.cc)for(let t of this.cc)c(t.email)||e.push(t.email);if(this.bcc)for(let t of this.bcc)c(t.email)||e.push(t.email);if(e.length>0)throw new S(`Invalid email address(es): ${e.join(", ")}`,e)}getEmailData(){this.resolveHeader();let e=["MIME-Version: 1.0"];for(let[o,y]of Object.entries(this.headers))e.push(`${o}: ${y}`);let t=this.generateSafeBoundary("mixed_"),i=this.generateSafeBoundary("related_"),s=this.generateSafeBoundary("alternative_"),a=this.attachments?.filter(o=>o.cid)||[],l=this.attachments?.filter(o=>!o.cid)||[],h=a.length>0,g=l.length>0;e.push(`Content-Type: multipart/mixed; boundary="${t}"`);let n=`${e.join(`\r
5
+ `)}\r
6
+ \r
7
+ `;if(n+=`--${t}\r
8
+ `,h&&(n+=`Content-Type: multipart/related; boundary="${i}"\r
9
+ \r
10
+ `,n+=`--${i}\r
11
+ `),n+=`Content-Type: multipart/alternative; boundary="${s}"\r
12
+ \r
13
+ `,this.text){n+=`--${s}\r
14
+ `,n+=`Content-Type: text/plain; charset="UTF-8"\r
15
+ `,n+=`Content-Transfer-Encoding: quoted-printable\r
16
+ \r
17
+ `;let o=R(this.text);n+=`${o}\r
18
+ \r
19
+ `}if(this.html){n+=`--${s}\r
20
+ `,n+=`Content-Type: text/html; charset="UTF-8"\r
21
+ `,n+=`Content-Transfer-Encoding: quoted-printable\r
22
+ \r
23
+ `;let o=R(this.html);n+=`${o}\r
24
+ \r
25
+ `}if(n+=`--${s}--\r
26
+ `,h){for(let o of a){let y=o.mimeType||this.getMimeType(o.filename);n+=`--${i}\r
27
+ `,n+=`Content-Type: ${y}; name="${o.filename}"\r
28
+ `,n+=`Content-Transfer-Encoding: base64\r
29
+ `,n+=`Content-ID: <${o.cid}>\r
30
+ `,n+=`Content-Disposition: inline; filename="${o.filename}"\r
31
+ \r
32
+ `;let v=o.content.match(/.{1,72}/g);v?n+=`${v.join(`\r
33
+ `)}`:n+=`${o.content}`,n+=`\r
34
+ \r
35
+ `}n+=`--${i}--\r
36
+ `}if(g)for(let o of l){let y=o.mimeType||this.getMimeType(o.filename);n+=`--${t}\r
37
+ `,n+=`Content-Type: ${y}; name="${o.filename}"\r
38
+ `,n+=`Content-Description: ${o.filename}\r
39
+ `,n+=`Content-Disposition: attachment; filename="${o.filename}";\r
40
+ `,n+=` creation-date="${new Date().toUTCString()}";\r
41
+ `,n+=`Content-Transfer-Encoding: base64\r
42
+ \r
43
+ `;let v=o.content.match(/.{1,72}/g);v?n+=`${v.join(`\r
44
+ `)}`:n+=`${o.content}`,n+=`\r
45
+ \r
46
+ `}return n+=`--${t}--\r
47
+ `,`${this.applyDotStuffing(n)}\r
48
+ .\r
49
+ `}applyDotStuffing(e){let t=e.replace(/\r\n\./g,`\r
50
+ ..`);return t.startsWith(".")&&(t=`.${t}`),t}generateSafeBoundary(e){let t=new Uint8Array(28);crypto.getRandomValues(t);let i=Array.from(t).map(a=>a.toString(16).padStart(2,"0")).join(""),s=e+i;return s=s.replace(/[<>@,;:\\/[\]?=" ]/g,"_"),s}getMimeType(e){let t=e.split(".").pop()?.toLowerCase();return{txt:"text/plain",html:"text/html",csv:"text/csv",pdf:"application/pdf",png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",zip:"application/zip"}[t||"txt"]||"application/octet-stream"}resolveHeader(){this.resolveFrom(),this.resolveTo(),this.resolveReply(),this.resolveCC(),this.resolveBCC(),this.resolveSubject(),this.headers.Date=this.headers.Date??new Date().toUTCString(),this.headers["Message-ID"]=this.headers["Message-ID"]??`<${crypto.randomUUID()}@${this.from.email.split("@").pop()}>`}resolveFrom(){if(this.headers.From)return;let e=this.from.email;this.from.name&&(e=`"${m(this.from.name)}" <${e}>`),this.headers.From=e}resolveTo(){if(this.headers.To)return;let e=this.to.map(t=>t.name?`"${m(t.name)}" <${t.email}>`:t.email);this.headers.To=e.join(", ")}resolveSubject(){this.headers.Subject||this.subject&&(this.headers.Subject=m(this.subject))}resolveReply(){if(!this.headers["Reply-To"]&&this.reply){let e=this.reply.email;this.reply.name&&(e=`"${m(this.reply.name)}" <${e}>`),this.headers["Reply-To"]=e}}resolveCC(){if(!this.headers.CC&&this.cc){let e=this.cc.map(t=>t.name?`"${m(t.name)}" <${t.email}>`:t.email);this.headers.CC=e.join(", ")}}resolveBCC(){if(!this.headers.BCC&&this.bcc){let e=this.bcc.map(t=>t.name?`"${m(t.name)}" <${t.email}>`:t.email);this.headers.BCC=e.join(", ")}}};var k=require("cloudflare:sockets");var U=(a=>(a[a.DEBUG=0]="DEBUG",a[a.INFO=1]="INFO",a[a.WARN=2]="WARN",a[a.ERROR=3]="ERROR",a[a.NONE=4]="NONE",a))(U||{}),x=class{constructor(e=1,t){this.level=e;this.prefix=t}level;prefix;debug(e,...t){this.level<=0&&console.debug(this.prefix+e,...t)}info(e,...t){this.level<=1&&console.info(this.prefix+e,...t)}warn(e,...t){this.level<=2&&console.warn(this.prefix+e,...t)}error(e,...t){this.level<=3&&console.error(this.prefix+e,...t)}};var I=class r{socket;host;port;secure;startTls;authType;credentials;socketTimeoutMs;responseTimeoutMs;reader;writer;logger;dsn;sendNotificationsTo;hooks;active=!1;emailSending=null;emailSendingOptions=null;emailToBeSent=new O;supportsDSN=!1;allowAuth=!1;authTypeSupported=[];supportsStartTls=!1;constructor(e){this.port=e.port,this.host=e.host,this.secure=!!e.secure,Array.isArray(e.authType)?this.authType=e.authType:typeof e.authType=="string"?this.authType=[e.authType]:this.authType=[],this.startTls=e.startTls===void 0?!0:e.startTls,this.credentials=e.credentials,this.dsn=e.dsn||{},this.hooks=e.hooks||{},this.socketTimeoutMs=e.socketTimeoutMs||6e4,this.responseTimeoutMs=e.responseTimeoutMs||3e4,this.socket=(0,k.connect)({hostname:this.host,port:this.port},{secureTransport:this.secure?"on":this.startTls?"starttls":"off",allowHalfOpen:!1}),this.reader=this.socket.readable.getReader(),this.writer=this.socket.writable.getWriter(),this.logger=new x(e.logLevel,`[WorkerMailer:${this.host}:${this.port}]`)}static async connect(e){let t=new r(e);return await t.initializeSmtpSession(),t.start().catch(console.error),t}send(e){let t=new C(e);return this.emailToBeSent.enqueue({email:t,options:e}),t.sent}static async send(e,t){let i=await r.connect(e);await i.send(t),await i.close()}async readTimeout(){return L(this.read(),this.responseTimeoutMs,new f("Timeout while waiting for smtp server response"))}async read(){let e="";for(;;){let{value:t}=await this.reader.read();if(!t)continue;let i=D(t).toString();if(this.logger.debug(`SMTP server response:
51
+ `+i),e=e+i,!e.endsWith(`
52
+ `))continue;let s=e.split(/\r?\n/),a=s[s.length-2];if(!/^\d+-/.test(a))return e}}async writeLine(e){await this.write(`${e}\r
53
+ `)}async write(e){this.logger.debug(`Write to socket:
54
+ `+e),await this.writer.write(u(e))}async initializeSmtpSession(){await this.waitForSocketConnected(),await this.greet(),await this.ehlo(),this.startTls&&!this.secure&&this.supportsStartTls&&(await this.tls(),await this.ehlo()),await this.auth(),this.active=!0,await this.hooks.onConnect?.()}async start(){for(;this.active;){let{email:e,options:t}=await this.emailToBeSent.dequeue();this.emailSending=e,this.emailSendingOptions=t;try{await this.mail(),await this.rcpt(),await this.data();let i=await this.body();this.emailSending.setSent(),await this.hooks.onSent?.(t,i)}catch(i){if(this.logger.error("Failed to send email: "+i.message),!this.active)return;this.emailSending.setSentError(i),await this.hooks.onError?.(t,i);try{await this.rset()}catch(s){await this.close(s)}}this.emailSending=null,this.emailSendingOptions=null}}async close(e){for(this.active=!1,this.logger.info("WorkerMailer is closed",e?.message||""),this.emailSending?.setSentError?.(e||new Error("WorkerMailer is shutting down"));this.emailToBeSent.length;){let{email:t}=await this.emailToBeSent.dequeue();t.setSentError(e||new Error("WorkerMailer is shutting down"))}try{await this.writeLine("QUIT"),await this.readTimeout(),this.socket.close().catch(()=>this.logger.error("Failed to close socket"))}catch{}await this.hooks.onClose?.(e)}async waitForSocketConnected(){this.logger.info("Connecting to SMTP server"),await L(this.socket.opened,this.socketTimeoutMs,new f("Socket timeout!")),this.logger.info("SMTP server connected")}async greet(){let e=await this.readTimeout();if(!e.startsWith("220"))throw new E("Failed to connect to SMTP server: "+e)}async ehlo(){await this.writeLine("EHLO 127.0.0.1");let e=await this.readTimeout();if(e.startsWith("421"))throw new Error(`Failed to EHLO. ${e}`);if(!e.startsWith("2")){await this.helo();return}this.parseCapabilities(e)}async helo(){await this.writeLine("HELO 127.0.0.1");let e=await this.readTimeout();if(!e.startsWith("2"))throw new Error(`Failed to HELO. ${e}`)}async tls(){await this.writeLine("STARTTLS");let e=await this.readTimeout();if(!e.startsWith("220"))throw new Error("Failed to start TLS: "+e);this.reader.releaseLock(),this.writer.releaseLock(),this.socket=this.socket.startTls(),this.reader=this.socket.readable.getReader(),this.writer=this.socket.writable.getWriter()}parseCapabilities(e){/[ -]AUTH\b/i.test(e)&&(this.allowAuth=!0),/[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)PLAIN/i.test(e)&&this.authTypeSupported.push("plain"),/[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)LOGIN/i.test(e)&&this.authTypeSupported.push("login"),/[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)CRAM-MD5/i.test(e)&&this.authTypeSupported.push("cram-md5"),/[ -]STARTTLS\b/i.test(e)&&(this.supportsStartTls=!0),/[ -]DSN\b/i.test(e)&&(this.supportsDSN=!0)}async auth(){if(this.allowAuth){if(!this.credentials)throw new d("smtp server requires authentication, but no credentials found");if(this.authTypeSupported.includes("plain")&&this.authType.includes("plain"))await this.authWithPlain();else if(this.authTypeSupported.includes("login")&&this.authType.includes("login"))await this.authWithLogin();else if(this.authTypeSupported.includes("cram-md5")&&this.authType.includes("cram-md5"))await this.authWithCramMD5();else throw new d("No supported auth method found.")}}async authWithPlain(){let e=btoa(`\0${this.credentials.username}\0${this.credentials.password}`);await this.writeLine(`AUTH PLAIN ${e}`);let t=await this.readTimeout();if(!t.startsWith("2"))throw new d(`Failed to plain authentication: ${t}`)}async authWithLogin(){await this.writeLine("AUTH LOGIN");let e=await this.readTimeout();if(!e.startsWith("3"))throw new d("Invalid login: "+e);let t=btoa(this.credentials.username);await this.writeLine(t);let i=await this.readTimeout();if(!i.startsWith("3"))throw new d("Failed to login authentication: "+i);let s=btoa(this.credentials.password);await this.writeLine(s);let a=await this.readTimeout();if(!a.startsWith("2"))throw new d("Failed to login authentication: "+a)}async authWithCramMD5(){await this.writeLine("AUTH CRAM-MD5");let e=await this.readTimeout(),t=e.match(/^334\s+(.+)$/)?.pop();if(!t)throw new d("Invalid CRAM-MD5 challenge: "+e);let i=atob(t),s=u(this.credentials.password),a=await crypto.subtle.importKey("raw",s,{name:"HMAC",hash:"MD5"},!1,["sign"]),l=u(i),h=await crypto.subtle.sign("HMAC",a,l),g=Array.from(new Uint8Array(h)).map(n=>n.toString(16).padStart(2,"0")).join("");await this.writeLine(btoa(`${this.credentials.username} ${g}`));let T=await this.readTimeout();if(!T.startsWith("2"))throw new d("Failed to cram-md5 authentication: "+T)}async mail(){let e=`MAIL FROM: <${this.emailSending.from.email}>`;this.supportsDSN&&(e+=` ${this.retBuilder()}`,this.emailSending?.dsnOverride?.envelopeId&&(e+=` ENVID=${this.emailSending?.dsnOverride?.envelopeId}`)),await this.writeLine(e);let t=await this.readTimeout();if(!t.startsWith("2"))throw new Error(`Invalid ${e} ${t}`)}async rcpt(){let e=[...this.emailSending.to,...this.emailSending.cc||[],...this.emailSending.bcc||[]];for(let t of e){let i=`RCPT TO: <${t.email}>`;this.supportsDSN&&(i+=this.notificationBuilder()),await this.writeLine(i);let s=await this.readTimeout();if(!s.startsWith("2"))throw new b(`Invalid ${i} ${s}`,t.email)}}async data(){await this.writeLine("DATA");let e=await this.readTimeout();if(!e.startsWith("3"))throw new Error(`Failed to send DATA: ${e}`)}async body(){await this.write(this.emailSending.getEmailData());let e=await this.readTimeout();if(!e.startsWith("2"))throw new Error("Failed send email body: "+e);return e}async rset(){await this.writeLine("RSET");let e=await this.readTimeout();if(!e.startsWith("2"))throw new Error(`Failed to reset: ${e}`)}notificationBuilder(){let e=[];return(this.emailSending?.dsnOverride?.NOTIFY&&this.emailSending?.dsnOverride?.NOTIFY?.SUCCESS||!this.emailSending?.dsnOverride?.NOTIFY&&this.dsn?.NOTIFY?.SUCCESS)&&e.push("SUCCESS"),(this.emailSending?.dsnOverride?.NOTIFY&&this.emailSending?.dsnOverride?.NOTIFY?.FAILURE||!this.emailSending?.dsnOverride?.NOTIFY&&this.dsn?.NOTIFY?.FAILURE)&&e.push("FAILURE"),(this.emailSending?.dsnOverride?.NOTIFY&&this.emailSending?.dsnOverride?.NOTIFY?.DELAY||!this.emailSending?.dsnOverride?.NOTIFY&&this.dsn?.NOTIFY?.DELAY)&&e.push("DELAY"),e.length>0?` NOTIFY=${e.join(",")}`:" NOTIFY=NEVER"}retBuilder(){let e=[];return(this.emailSending?.dsnOverride?.RET&&this.emailSending?.dsnOverride?.RET?.HEADERS||!this.emailSending?.dsnOverride?.RET&&this.dsn?.RET?.HEADERS)&&e.push("HDRS"),(this.emailSending?.dsnOverride?.RET&&this.emailSending?.dsnOverride?.RET?.FULL||!this.emailSending?.dsnOverride?.RET&&this.dsn?.RET?.FULL)&&e.push("FULL"),e.length>0?`RET=${e.join(",")}`:""}};0&&(module.exports={Email,InvalidContentError,InvalidEmailError,LogLevel,SmtpAuthError,SmtpConnectionError,SmtpRecipientError,SmtpTimeoutError,WorkerMailer,WorkerMailerError,encodeHeader,isValidEmail,validateEmails});
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import{a as o,b as r,c as m,d as t,e as a,f,g as i,h as l,i as p,j as x,k as d,l as e,m as s}from"./chunk-TXGT6IDZ.mjs";export{d as Email,p as InvalidContentError,t as InvalidEmailError,e as LogLevel,a as SmtpAuthError,f as SmtpConnectionError,i as SmtpRecipientError,l as SmtpTimeoutError,s as WorkerMailer,m as WorkerMailerError,x as encodeHeader,o as isValidEmail,r as validateEmails};
@@ -0,0 +1,186 @@
1
+ declare function encodeHeader(text: string): string;
2
+ type User = {
3
+ name?: string;
4
+ email: string;
5
+ };
6
+ type Attachment = {
7
+ filename: string;
8
+ content: string;
9
+ mimeType?: string;
10
+ /** Content-ID for inline images (e.g., 'logo@company' for use as <img src="cid:logo@company">) */
11
+ cid?: string;
12
+ /** If true, attachment will be inline (requires cid) */
13
+ inline?: boolean;
14
+ };
15
+ type EmailOptions = {
16
+ from: string | User;
17
+ to: string | string[] | User | User[];
18
+ reply?: string | User;
19
+ cc?: string | string[] | User | User[];
20
+ bcc?: string | string[] | User | User[];
21
+ subject: string;
22
+ text?: string;
23
+ html?: string;
24
+ headers?: Record<string, string>;
25
+ attachments?: Attachment[];
26
+ dsnOverride?: {
27
+ envelopeId?: string;
28
+ RET?: {
29
+ HEADERS?: boolean;
30
+ FULL?: boolean;
31
+ };
32
+ NOTIFY?: {
33
+ DELAY?: boolean;
34
+ FAILURE?: boolean;
35
+ SUCCESS?: boolean;
36
+ };
37
+ };
38
+ };
39
+ declare class Email {
40
+ readonly from: User;
41
+ readonly to: User[];
42
+ readonly reply?: User;
43
+ readonly cc?: User[];
44
+ readonly bcc?: User[];
45
+ readonly subject: string;
46
+ readonly text?: string;
47
+ readonly html?: string;
48
+ readonly dsnOverride?: {
49
+ envelopeId?: string;
50
+ RET?: {
51
+ HEADERS?: boolean;
52
+ FULL?: boolean;
53
+ };
54
+ NOTIFY?: {
55
+ DELAY?: boolean;
56
+ FAILURE?: boolean;
57
+ SUCCESS?: boolean;
58
+ };
59
+ };
60
+ readonly attachments?: Attachment[];
61
+ readonly headers: Record<string, string>;
62
+ setSent: () => void;
63
+ setSentError: (e: unknown) => void;
64
+ sent: Promise<void>;
65
+ constructor(options: EmailOptions);
66
+ private static toUsers;
67
+ private validateEmails;
68
+ getEmailData(): string;
69
+ private applyDotStuffing;
70
+ private generateSafeBoundary;
71
+ private getMimeType;
72
+ private resolveHeader;
73
+ private resolveFrom;
74
+ private resolveTo;
75
+ private resolveSubject;
76
+ private resolveReply;
77
+ private resolveCC;
78
+ private resolveBCC;
79
+ }
80
+
81
+ declare enum LogLevel {
82
+ DEBUG = 0,
83
+ INFO = 1,
84
+ WARN = 2,
85
+ ERROR = 3,
86
+ NONE = 4
87
+ }
88
+
89
+ type AuthType = 'plain' | 'login' | 'cram-md5';
90
+ type Credentials = {
91
+ username: string;
92
+ password: string;
93
+ };
94
+ /**
95
+ * Hooks for WorkerMailer events
96
+ */
97
+ type WorkerMailerHooks = {
98
+ /** Called when a connection to SMTP server is established */
99
+ onConnect?: () => void | Promise<void>;
100
+ /** Called when an email is successfully sent */
101
+ onSent?: (email: EmailOptions, response: string) => void | Promise<void>;
102
+ /** Called when an email fails to send */
103
+ onError?: (email: EmailOptions | null, error: Error) => void | Promise<void>;
104
+ /** Called when the connection is closed */
105
+ onClose?: (error?: Error) => void | Promise<void>;
106
+ };
107
+ type WorkerMailerOptions = {
108
+ host: string;
109
+ port: number;
110
+ secure?: boolean;
111
+ startTls?: boolean;
112
+ credentials?: Credentials;
113
+ authType?: AuthType | AuthType[];
114
+ logLevel?: LogLevel;
115
+ dsn?: {
116
+ RET?: {
117
+ HEADERS?: boolean;
118
+ FULL?: boolean;
119
+ } | undefined;
120
+ NOTIFY?: {
121
+ DELAY?: boolean;
122
+ FAILURE?: boolean;
123
+ SUCCESS?: boolean;
124
+ } | undefined;
125
+ } | undefined;
126
+ socketTimeoutMs?: number;
127
+ responseTimeoutMs?: number;
128
+ /** Event hooks for monitoring email operations */
129
+ hooks?: WorkerMailerHooks;
130
+ };
131
+ declare class WorkerMailer {
132
+ private socket;
133
+ private readonly host;
134
+ private readonly port;
135
+ private readonly secure;
136
+ private readonly startTls;
137
+ private readonly authType;
138
+ private readonly credentials?;
139
+ private readonly socketTimeoutMs;
140
+ private readonly responseTimeoutMs;
141
+ private reader;
142
+ private writer;
143
+ private readonly logger;
144
+ private readonly dsn;
145
+ private readonly sendNotificationsTo;
146
+ private readonly hooks;
147
+ private active;
148
+ private emailSending;
149
+ private emailSendingOptions;
150
+ private emailToBeSent;
151
+ /** SMTP server capabilities **/
152
+ private supportsDSN;
153
+ private allowAuth;
154
+ private authTypeSupported;
155
+ private supportsStartTls;
156
+ private constructor();
157
+ static connect(options: WorkerMailerOptions): Promise<WorkerMailer>;
158
+ send(options: EmailOptions): Promise<void>;
159
+ static send(options: WorkerMailerOptions, email: EmailOptions): Promise<void>;
160
+ private readTimeout;
161
+ private read;
162
+ private writeLine;
163
+ private write;
164
+ private initializeSmtpSession;
165
+ private start;
166
+ close(error?: Error): Promise<void>;
167
+ private waitForSocketConnected;
168
+ private greet;
169
+ private ehlo;
170
+ private helo;
171
+ private tls;
172
+ private parseCapabilities;
173
+ private auth;
174
+ private authWithPlain;
175
+ private authWithLogin;
176
+ private authWithCramMD5;
177
+ private mail;
178
+ private rcpt;
179
+ private data;
180
+ private body;
181
+ private rset;
182
+ private notificationBuilder;
183
+ private retBuilder;
184
+ }
185
+
186
+ export { type Attachment as A, type Credentials as C, Email as E, LogLevel as L, type User as U, WorkerMailer as W, type AuthType as a, type EmailOptions as b, type WorkerMailerHooks as c, type WorkerMailerOptions as d, encodeHeader as e };
@@ -0,0 +1,186 @@
1
+ declare function encodeHeader(text: string): string;
2
+ type User = {
3
+ name?: string;
4
+ email: string;
5
+ };
6
+ type Attachment = {
7
+ filename: string;
8
+ content: string;
9
+ mimeType?: string;
10
+ /** Content-ID for inline images (e.g., 'logo@company' for use as <img src="cid:logo@company">) */
11
+ cid?: string;
12
+ /** If true, attachment will be inline (requires cid) */
13
+ inline?: boolean;
14
+ };
15
+ type EmailOptions = {
16
+ from: string | User;
17
+ to: string | string[] | User | User[];
18
+ reply?: string | User;
19
+ cc?: string | string[] | User | User[];
20
+ bcc?: string | string[] | User | User[];
21
+ subject: string;
22
+ text?: string;
23
+ html?: string;
24
+ headers?: Record<string, string>;
25
+ attachments?: Attachment[];
26
+ dsnOverride?: {
27
+ envelopeId?: string;
28
+ RET?: {
29
+ HEADERS?: boolean;
30
+ FULL?: boolean;
31
+ };
32
+ NOTIFY?: {
33
+ DELAY?: boolean;
34
+ FAILURE?: boolean;
35
+ SUCCESS?: boolean;
36
+ };
37
+ };
38
+ };
39
+ declare class Email {
40
+ readonly from: User;
41
+ readonly to: User[];
42
+ readonly reply?: User;
43
+ readonly cc?: User[];
44
+ readonly bcc?: User[];
45
+ readonly subject: string;
46
+ readonly text?: string;
47
+ readonly html?: string;
48
+ readonly dsnOverride?: {
49
+ envelopeId?: string;
50
+ RET?: {
51
+ HEADERS?: boolean;
52
+ FULL?: boolean;
53
+ };
54
+ NOTIFY?: {
55
+ DELAY?: boolean;
56
+ FAILURE?: boolean;
57
+ SUCCESS?: boolean;
58
+ };
59
+ };
60
+ readonly attachments?: Attachment[];
61
+ readonly headers: Record<string, string>;
62
+ setSent: () => void;
63
+ setSentError: (e: unknown) => void;
64
+ sent: Promise<void>;
65
+ constructor(options: EmailOptions);
66
+ private static toUsers;
67
+ private validateEmails;
68
+ getEmailData(): string;
69
+ private applyDotStuffing;
70
+ private generateSafeBoundary;
71
+ private getMimeType;
72
+ private resolveHeader;
73
+ private resolveFrom;
74
+ private resolveTo;
75
+ private resolveSubject;
76
+ private resolveReply;
77
+ private resolveCC;
78
+ private resolveBCC;
79
+ }
80
+
81
+ declare enum LogLevel {
82
+ DEBUG = 0,
83
+ INFO = 1,
84
+ WARN = 2,
85
+ ERROR = 3,
86
+ NONE = 4
87
+ }
88
+
89
+ type AuthType = 'plain' | 'login' | 'cram-md5';
90
+ type Credentials = {
91
+ username: string;
92
+ password: string;
93
+ };
94
+ /**
95
+ * Hooks for WorkerMailer events
96
+ */
97
+ type WorkerMailerHooks = {
98
+ /** Called when a connection to SMTP server is established */
99
+ onConnect?: () => void | Promise<void>;
100
+ /** Called when an email is successfully sent */
101
+ onSent?: (email: EmailOptions, response: string) => void | Promise<void>;
102
+ /** Called when an email fails to send */
103
+ onError?: (email: EmailOptions | null, error: Error) => void | Promise<void>;
104
+ /** Called when the connection is closed */
105
+ onClose?: (error?: Error) => void | Promise<void>;
106
+ };
107
+ type WorkerMailerOptions = {
108
+ host: string;
109
+ port: number;
110
+ secure?: boolean;
111
+ startTls?: boolean;
112
+ credentials?: Credentials;
113
+ authType?: AuthType | AuthType[];
114
+ logLevel?: LogLevel;
115
+ dsn?: {
116
+ RET?: {
117
+ HEADERS?: boolean;
118
+ FULL?: boolean;
119
+ } | undefined;
120
+ NOTIFY?: {
121
+ DELAY?: boolean;
122
+ FAILURE?: boolean;
123
+ SUCCESS?: boolean;
124
+ } | undefined;
125
+ } | undefined;
126
+ socketTimeoutMs?: number;
127
+ responseTimeoutMs?: number;
128
+ /** Event hooks for monitoring email operations */
129
+ hooks?: WorkerMailerHooks;
130
+ };
131
+ declare class WorkerMailer {
132
+ private socket;
133
+ private readonly host;
134
+ private readonly port;
135
+ private readonly secure;
136
+ private readonly startTls;
137
+ private readonly authType;
138
+ private readonly credentials?;
139
+ private readonly socketTimeoutMs;
140
+ private readonly responseTimeoutMs;
141
+ private reader;
142
+ private writer;
143
+ private readonly logger;
144
+ private readonly dsn;
145
+ private readonly sendNotificationsTo;
146
+ private readonly hooks;
147
+ private active;
148
+ private emailSending;
149
+ private emailSendingOptions;
150
+ private emailToBeSent;
151
+ /** SMTP server capabilities **/
152
+ private supportsDSN;
153
+ private allowAuth;
154
+ private authTypeSupported;
155
+ private supportsStartTls;
156
+ private constructor();
157
+ static connect(options: WorkerMailerOptions): Promise<WorkerMailer>;
158
+ send(options: EmailOptions): Promise<void>;
159
+ static send(options: WorkerMailerOptions, email: EmailOptions): Promise<void>;
160
+ private readTimeout;
161
+ private read;
162
+ private writeLine;
163
+ private write;
164
+ private initializeSmtpSession;
165
+ private start;
166
+ close(error?: Error): Promise<void>;
167
+ private waitForSocketConnected;
168
+ private greet;
169
+ private ehlo;
170
+ private helo;
171
+ private tls;
172
+ private parseCapabilities;
173
+ private auth;
174
+ private authWithPlain;
175
+ private authWithLogin;
176
+ private authWithCramMD5;
177
+ private mail;
178
+ private rcpt;
179
+ private data;
180
+ private body;
181
+ private rset;
182
+ private notificationBuilder;
183
+ private retBuilder;
184
+ }
185
+
186
+ export { type Attachment as A, type Credentials as C, Email as E, LogLevel as L, type User as U, WorkerMailer as W, type AuthType as a, type EmailOptions as b, type WorkerMailerHooks as c, type WorkerMailerOptions as d, encodeHeader as e };
@@ -0,0 +1,93 @@
1
+ import { d as WorkerMailerOptions, b as EmailOptions } from './mailer-CNOSp-w6.mjs';
2
+
3
+ /**
4
+ * Message format for the email queue
5
+ */
6
+ type QueueEmailMessage = {
7
+ mailerOptions: WorkerMailerOptions;
8
+ emailOptions: EmailOptions;
9
+ };
10
+ /**
11
+ * Result of processing a queued email
12
+ */
13
+ type QueueProcessResult = {
14
+ success: boolean;
15
+ error?: string;
16
+ emailOptions: EmailOptions;
17
+ };
18
+ /**
19
+ * Creates a queue handler for processing emails asynchronously via Cloudflare Queues.
20
+ *
21
+ * This is useful for:
22
+ * - Offloading email sending from the main request
23
+ * - Handling email sending with automatic retries (via Queue retry policies)
24
+ * - Processing emails in batches
25
+ *
26
+ * @example
27
+ * ```typescript
28
+ * // In your worker:
29
+ * import { createQueueHandler } from '@ribassu/worker-mailer/queue'
30
+ *
31
+ * export default {
32
+ * async queue(batch, env, ctx) {
33
+ * const handler = createQueueHandler()
34
+ * const results = await handler(batch)
35
+ * console.log('Processed emails:', results)
36
+ * }
37
+ * }
38
+ *
39
+ * // To enqueue an email:
40
+ * await env.EMAIL_QUEUE.send({
41
+ * mailerOptions: {
42
+ * host: 'smtp.example.com',
43
+ * port: 587,
44
+ * credentials: { username: 'user', password: 'pass' },
45
+ * authType: 'plain'
46
+ * },
47
+ * emailOptions: {
48
+ * from: 'sender@example.com',
49
+ * to: 'recipient@example.com',
50
+ * subject: 'Hello',
51
+ * text: 'World'
52
+ * }
53
+ * })
54
+ * ```
55
+ *
56
+ * @param onSuccess - Optional callback when email is sent successfully
57
+ * @param onError - Optional callback when email fails to send
58
+ * @returns A queue handler function
59
+ */
60
+ declare function createQueueHandler(options?: {
61
+ onSuccess?: (result: QueueProcessResult) => void | Promise<void>;
62
+ onError?: (result: QueueProcessResult) => void | Promise<void>;
63
+ }): (batch: MessageBatch<QueueEmailMessage>) => Promise<QueueProcessResult[]>;
64
+ /**
65
+ * Helper to enqueue an email for async processing.
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * import { enqueueEmail } from '@ribassu/worker-mailer/queue'
70
+ *
71
+ * await enqueueEmail(env.EMAIL_QUEUE, {
72
+ * mailerOptions: { host: 'smtp.example.com', port: 587, ... },
73
+ * emailOptions: { from: '...', to: '...', subject: '...', text: '...' }
74
+ * })
75
+ * ```
76
+ */
77
+ declare function enqueueEmail(queue: Queue<QueueEmailMessage>, message: QueueEmailMessage): Promise<void>;
78
+ /**
79
+ * Helper to enqueue multiple emails for async processing.
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * import { enqueueEmails } from '@ribassu/worker-mailer/queue'
84
+ *
85
+ * await enqueueEmails(env.EMAIL_QUEUE, [
86
+ * { mailerOptions: {...}, emailOptions: {...} },
87
+ * { mailerOptions: {...}, emailOptions: {...} },
88
+ * ])
89
+ * ```
90
+ */
91
+ declare function enqueueEmails(queue: Queue<QueueEmailMessage>, messages: QueueEmailMessage[]): Promise<void>;
92
+
93
+ export { type QueueEmailMessage, type QueueProcessResult, createQueueHandler, enqueueEmail, enqueueEmails };