edge-mailer 0.6.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.
- package/CHANGELOG.md +8 -0
- package/CLIENT-INTEGRATION.md +347 -0
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/dist/chunk-MKG2NZOU.mjs +1 -0
- package/dist/chunk-U6ERJXK6.mjs +67 -0
- package/dist/cloudflare.d.mts +2 -0
- package/dist/cloudflare.d.ts +2 -0
- package/dist/cloudflare.js +67 -0
- package/dist/cloudflare.mjs +1 -0
- package/dist/deno.d.mts +34 -0
- package/dist/deno.d.ts +34 -0
- package/dist/deno.js +67 -0
- package/dist/deno.mjs +1 -0
- package/dist/index.d.mts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +67 -0
- package/dist/index.mjs +1 -0
- package/dist/pool-CcxclJQJ.d.mts +323 -0
- package/dist/pool-CcxclJQJ.d.ts +323 -0
- package/package.json +89 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";var U=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var Q=Object.prototype.hasOwnProperty;var _=(i,e)=>{for(var t in e)U(i,t,{get:e[t],enumerable:!0})},G=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of V(e))!Q.call(i,s)&&s!==t&&U(i,s,{get:()=>e[s],enumerable:!(r=K(e,s))||r.enumerable});return i};var Z=i=>G(U({},"__esModule",{value:!0}),i);var ve={};_(ve,{EdgeMailer:()=>L,Email:()=>S,LogLevel:()=>$,SMTPError:()=>c,SmtpConnectionPool:()=>C,SmtpMailer:()=>x,cloudflareSocketConnector:()=>z,encodeHeader:()=>v,signDkimMessage:()=>B,validateDkimConfig:()=>O});module.exports=Z(ve);var X=new TextEncoder;function g(i){return X.encode(i)}var J=new TextDecoder("utf-8");function P(i){return J.decode(i)}function R(i,e=76){let t=g(i),r="",s=0,n=0;for(;n<t.length;){let a=t[n],o;if(a===10){r+=`\r
|
|
2
|
+
`,s=0,n++;continue}else if(a===13)if(n+1<t.length&&t[n+1]===10){r+=`\r
|
|
3
|
+
`,s=0,n+=2;continue}else o="=0D";if(o===void 0){let d=a===32||a===9,p=n+1>=t.length||t[n+1]===10||t[n+1]===13;a<32&&!d||a>126||a===61||d&&p?o=`=${a.toString(16).toUpperCase().padStart(2,"0")}`:o=String.fromCharCode(a)}s+o.length>e-3&&(r+=`=\r
|
|
4
|
+
`,s=0),r+=o,s+=o.length,n++}return r}function v(i){if(!/[^\x00-\x7F]/.test(i))return i;let e=g(i),t="";for(let r of e)r>=33&&r<=126&&r!==63&&r!==61&&r!==95?t+=String.fromCharCode(r):r===32?t+="_":t+=`=${r.toString(16).toUpperCase().padStart(2,"0")}`;return`=?UTF-8?Q?${t}?=`}var S=class i{from;to;reply;cc;bcc;subject;text;html;envelope;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 Error("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=i.toUsers(e.to),this.cc=i.toUsers(e.cc),this.bcc=i.toUsers(e.bcc),this.subject=e.subject,this.text=e.text,this.html=e.html,this.attachments=e.attachments,this.envelope=e.envelope?{...e.envelope,to:i.toEnvelopeRecipients(e.envelope.to)}:void 0,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]}static toEnvelopeRecipients(e){if(e)return Array.isArray(e)?e:[e]}getMessageData(){return this.buildMessageData(this.resolveAttachmentsSync())}async getMessageDataAsync(){return this.buildMessageData(await this.resolveAttachments())}buildMessageData(e){this.resolveHeader();let t=["MIME-Version: 1.0"];for(let[l,f]of Object.entries(this.headers))l.toLowerCase()!=="bcc"&&t.push(`${l}: ${f}`);let r=this.generateSafeBoundary("mixed_"),s=this.generateSafeBoundary("alternative_");t.push(`Content-Type: multipart/mixed; boundary="${r}"`);let a=`${t.join(`\r
|
|
5
|
+
`)}\r
|
|
6
|
+
\r
|
|
7
|
+
`,o=(e||[]).filter(l=>this.attachmentDisposition(l)==="inline"),d=(e||[]).filter(l=>this.attachmentDisposition(l)!=="inline"),p=this.generateSafeBoundary("related_");if(o.length?(a+=`--${r}\r
|
|
8
|
+
`,a+=`Content-Type: multipart/related; boundary="${p}"\r
|
|
9
|
+
\r
|
|
10
|
+
`,a+=`--${p}\r
|
|
11
|
+
`,a+=`Content-Type: multipart/alternative; boundary="${s}"\r
|
|
12
|
+
\r
|
|
13
|
+
`):(a+=`--${r}\r
|
|
14
|
+
`,a+=`Content-Type: multipart/alternative; boundary="${s}"\r
|
|
15
|
+
\r
|
|
16
|
+
`),this.text){a+=`--${s}\r
|
|
17
|
+
`,a+=`Content-Type: text/plain; charset="UTF-8"\r
|
|
18
|
+
`,a+=`Content-Transfer-Encoding: quoted-printable\r
|
|
19
|
+
\r
|
|
20
|
+
`;let l=R(this.text);a+=`${l}\r
|
|
21
|
+
\r
|
|
22
|
+
`}if(this.html){a+=`--${s}\r
|
|
23
|
+
`,a+=`Content-Type: text/html; charset="UTF-8"\r
|
|
24
|
+
`,a+=`Content-Transfer-Encoding: quoted-printable\r
|
|
25
|
+
\r
|
|
26
|
+
`;let l=R(this.html);a+=`${l}\r
|
|
27
|
+
\r
|
|
28
|
+
`}if(a+=`--${s}--\r
|
|
29
|
+
`,o.length){for(let l of o)a+=this.attachmentPart(p,l);a+=`--${p}--\r
|
|
30
|
+
`}for(let l of d)a+=this.attachmentPart(r,l);return a+=`--${r}--\r
|
|
31
|
+
`,a.endsWith(`\r
|
|
32
|
+
`)?a:`${a}\r
|
|
33
|
+
`}async resolveAttachments(){if(!this.attachments?.length)return;let e=[];for(let t of this.attachments){if(typeof t.content=="string"){e.push({...t,content:t.content});continue}if(this.isBlob(t.content)){let r=t.content;e.push({...t,content:new Uint8Array(await r.arrayBuffer()),resolvedContentType:r.type||void 0});continue}e.push({...t,content:this.attachmentBytes(t.content)})}return e}resolveAttachmentsSync(){if(this.attachments?.length)return this.attachments.map(e=>{if(typeof e.content=="string")return{...e,content:e.content};if(this.isBlob(e.content))throw new Error("Blob attachment content requires async message generation; use getMessageDataAsync(), getEmailDataAsync(), or send through a mailer");return{...e,content:this.attachmentBytes(e.content)}})}attachmentBytes(e){return e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):new Uint8Array(e.buffer,e.byteOffset,e.byteLength)}isBlob(e){return typeof Blob<"u"&&e instanceof Blob}attachmentPart(e,t){let r=t.mimeType||t.contentType||t.resolvedContentType||this.getMimeType(t.filename),s=this.attachmentDisposition(t),n=t.encoding||"base64",a=`--${e}\r
|
|
34
|
+
`;return a+=`Content-Type: ${r}; name="${t.filename}"\r
|
|
35
|
+
`,a+=`Content-Description: ${t.filename}\r
|
|
36
|
+
`,t.contentId&&(a+=`Content-ID: <${t.contentId.replace(/[<>]/g,"")}>\r
|
|
37
|
+
`),a+=`Content-Disposition: ${s}; filename="${t.filename}";\r
|
|
38
|
+
`,a+=` creation-date="${new Date().toUTCString()}";\r
|
|
39
|
+
`,a+=`Content-Transfer-Encoding: ${n}\r
|
|
40
|
+
\r
|
|
41
|
+
`,a+=`${this.encodedAttachmentContent(t,n)}\r
|
|
42
|
+
\r
|
|
43
|
+
`,a}attachmentDisposition(e){return e.disposition||(e.contentId?"inline":"attachment")}encodedAttachmentContent(e,t){if(t==="base64")return typeof e.content=="string"?this.wrapBase64(e.content):this.bytesToWrappedBase64(e.content);let r=this.attachmentTextContent(e);if(t==="quoted-printable")return R(r);if(/[^\x00-\x7F]/.test(r))throw new Error("7bit attachment content must contain ASCII only");return r.replace(/\r?\n/g,`\r
|
|
44
|
+
`)}attachmentTextContent(e){return typeof e.content=="string"?e.content:P(e.content)}bytesToWrappedBase64(e){let t="";for(let r=0;r<e.length;r+=57)t&&(t+=`\r
|
|
45
|
+
`),t+=this.bytesToBase64(e.subarray(r,r+57));return t}bytesToBase64(e){let t="";for(let r=0;r<e.length;r+=32768)t+=String.fromCharCode(...e.subarray(r,r+32768));return btoa(t)}wrapBase64(e){let t="",r="";for(let s of e)/\s/.test(s)||(r+=s,r.length===76&&(t+=t?`\r
|
|
46
|
+
${r}`:r,r=""));return r&&(t+=t?`\r
|
|
47
|
+
${r}`:r),t}getEmailData(){return i.toSmtpData(this.getMessageData())}async getEmailDataAsync(){return i.toSmtpData(await this.getMessageDataAsync())}static toSmtpData(e){let t=i.applyDotStuffing(e);return t.endsWith(`\r
|
|
48
|
+
`)?`${t}.\r
|
|
49
|
+
`:`${t}\r
|
|
50
|
+
.\r
|
|
51
|
+
`}static applyDotStuffing(e){let t=e.replace(/\r\n\./g,`\r
|
|
52
|
+
..`);return t.startsWith(".")&&(t=`.${t}`),t}generateSafeBoundary(e){let t=new Uint8Array(28);crypto.getRandomValues(t);let r=Array.from(t).map(n=>n.toString(16).padStart(2,"0")).join(""),s=e+r;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.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=`"${v(this.from.name)}" <${e}>`),this.headers.From=e}resolveTo(){if(this.headers.To)return;let e=this.to.map(t=>t.name?`"${v(t.name)}" <${t.email}>`:t.email);this.headers.To=e.join(", ")}resolveSubject(){this.headers.Subject||this.subject&&(this.headers.Subject=v(this.subject))}resolveReply(){if(!this.headers["Reply-To"]&&this.reply){let e=this.reply.email;this.reply.name&&(e=`"${v(this.reply.name)}" <${e}>`),this.headers["Reply-To"]=e}}resolveCC(){if(!this.headers.CC&&this.cc){let e=this.cc.map(t=>t.name?`"${v(t.name)}" <${t.email}>`:t.email);this.headers.CC=e.join(", ")}}};var ee=["from","to","subject","date","message-id","mime-version","content-type"];function I(...i){let e=i.reduce((s,n)=>s+n.length,0),t=new Uint8Array(e),r=0;for(let s of i)t.set(s,r),r+=s.length;return t}function te(i){let e=atob(i),t=new Uint8Array(e.length);for(let r=0;r<e.length;r++)t[r]=e.charCodeAt(r);return t}function F(i){let e=i instanceof Uint8Array?i:new Uint8Array(i),t="";for(let r=0;r<e.length;r+=32768)t+=String.fromCharCode(...e.subarray(r,r+32768));return btoa(t)}function N(i){return new Uint8Array(g(i))}function re(i){if(i<128)return new Uint8Array([i]);let e=[],t=i;for(;t>0;)e.unshift(t&255),t>>=8;return new Uint8Array([128|e.length,...e])}function D(i,e){return I(new Uint8Array([i]),re(e.length),e)}function ie(i){let e=new Uint8Array([2,1,0]),t=new Uint8Array([6,9,42,134,72,134,247,13,1,1,1]),r=new Uint8Array([5,0]),s=D(48,I(t,r)),n=D(4,i);return D(48,I(e,s,n))}function se(i){let e=i.match(/-----BEGIN (PRIVATE KEY|RSA PRIVATE KEY)-----([\s\S]+?)-----END \1-----/);if(!e)throw new Error("DKIM privateKey must be a PEM private key");let t=e[1],r=te(e[2].replace(/\s+/g,""));if(t==="PRIVATE KEY")return r;if(t==="RSA PRIVATE KEY")return ie(r);throw new Error(`Unsupported DKIM private key type: ${t}`)}function j(i){return i.replace(/\r?\n/g,`\r
|
|
53
|
+
`)}function ne(i){let e=[];for(let t of j(i).split(`\r
|
|
54
|
+
`)){if(/^[ \t]/.test(t)&&e.length){let s=e[e.length-1];s.value+=`\r
|
|
55
|
+
${t}`;continue}let r=t.indexOf(":");r<1||e.push({name:t.slice(0,r),value:t.slice(r+1)})}return e}function ae(i){let e=i.name.trim().toLowerCase(),t=i.value.replace(/\r\n[ \t]+/g," ").replace(/[ \t]+/g," ").trim();return`${e}:${t}`}function oe(i){let e=j(i).split(`\r
|
|
56
|
+
`).map(t=>t.replace(/[ \t]+$/g,"").replace(/[ \t]+/g," "));for(;e.length>0&&e[e.length-1]==="";)e.pop();return e.length?`${e.join(`\r
|
|
57
|
+
`)}\r
|
|
58
|
+
`:`\r
|
|
59
|
+
`}function ce(i,e){let t=e&&e.length?e.map(n=>n.toLowerCase()):ee,r=new Set,s=[];for(let n of t)for(let a=i.length-1;a>=0;a--)if(!r.has(a)&&i[a].name.trim().toLowerCase()===n){r.add(a),s.push(i[a]);break}return s}function O(i){if(i){if(!i.domainName?.trim())throw new Error("DKIM domainName is required");if(!i.keySelector?.trim())throw new Error("DKIM keySelector is required");if(!i.privateKey?.trim())throw new Error("DKIM privateKey is required");for(let e of[i.domainName,i.keySelector])if(/[\r\n;]/.test(e))throw new Error("DKIM domainName and keySelector must be header safe")}}async function B(i,e){O(e);let t=i.indexOf(`\r
|
|
60
|
+
\r
|
|
61
|
+
`);if(t<0)throw new Error("Unable to DKIM sign message without headers and body");let r=i.slice(0,t),s=i.slice(t+4),n=ne(r),a=ce(n,e.headerFieldNames);if(!a.some(m=>m.name.toLowerCase()==="from"))throw new Error("DKIM signing requires a From header");let o=oe(s),d=await crypto.subtle.digest("SHA-256",N(o)),p=a.map(m=>m.name.trim().toLowerCase()),l=`v=1; a=rsa-sha256; c=relaxed/relaxed; d=${e.domainName}; s=${e.keySelector}; h=${p.join(":")}; bh=${F(d)}; b=`,f=[...a.map(ae),`dkim-signature:${l}`].join(`\r
|
|
62
|
+
`),h=await crypto.subtle.importKey("pkcs8",se(e.privateKey),{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["sign"]),b=await crypto.subtle.sign("RSASSA-PKCS1-v1_5",h,N(f));return`DKIM-Signature: ${l}${F(b)}\r
|
|
63
|
+
${i}`}var q=require("cloudflare:sockets");var $=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.NONE=4]="NONE",n))($||{}),A=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 le=["plain","login","cram-md5"],c=class extends Error{stage;command;response;responseCode;enhancedStatusCode;transient;cause;constructor(e,t){super(e),this.name="SMTPError",this.stage=t.stage,this.command=t.command,this.response=t.response,this.cause=t.cause;let r=t.response?.match(/^(\d{3})/);this.responseCode=r?Number(r[1]):void 0,this.enhancedStatusCode=t.response?.match(/^\d{3}[ -]([245]\.\d{1,3}\.\d{1,3})\b/)?.at(1),this.transient=this.responseCode?this.responseCode>=400&&this.responseCode<500:!1}},de=[7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21],pe=Array.from({length:64},(i,e)=>Math.floor(Math.abs(Math.sin(e+1))*4294967296)>>>0);function he(i,e){return(i<<e|i>>>32-e)>>>0}function W(...i){let e=i.reduce((s,n)=>s+n.length,0),t=new Uint8Array(e),r=0;for(let s of i)t.set(s,r),r+=s.length;return t}function k(i){let e=BigInt(i.length)*8n,t=i.length+1;for(;t%64!==56;)t++;let r=new Uint8Array(t+8);r.set(i),r[i.length]=128;let s=new DataView(r.buffer);for(let h=0;h<8;h++)s.setUint8(t+h,Number(e>>BigInt(h*8)&0xffn));let n=1732584193,a=4023233417,o=2562383102,d=271733878,p=new Array(16);for(let h=0;h<r.length;h+=64){for(let u=0;u<16;u++)p[u]=s.getUint32(h+u*4,!0);let b=n,m=a,w=o,y=d;for(let u=0;u<64;u++){let E,M;u<16?(E=m&w|~m&y,M=u):u<32?(E=y&m|~y&w,M=(5*u+1)%16):u<48?(E=m^w^y,M=(3*u+5)%16):(E=w^(m|~y),M=7*u%16);let Y=y;y=w,w=m,m=m+he(b+E+pe[u]+p[M]>>>0,de[u])>>>0,b=Y}n=n+b>>>0,a=a+m>>>0,o=o+w>>>0,d=d+y>>>0}let l=new Uint8Array(16),f=new DataView(l.buffer);return f.setUint32(0,n,!0),f.setUint32(4,a,!0),f.setUint32(8,o,!0),f.setUint32(12,d,!0),l}function ue(i){return Array.from(i).map(e=>e.toString(16).padStart(2,"0")).join("")}function me(i,e){let r=i.length>64?k(i):i,s=new Uint8Array(64).fill(92),n=new Uint8Array(64).fill(54);return r.forEach((a,o)=>{s[o]^=a,n[o]^=a}),ue(k(W(s,k(W(n,e)))))}function fe(i){let e=new Uint8Array(i.length);for(let t=0;t<i.length;t++)e[t]=i.charCodeAt(t)&255;return e}function ge(i){return fe(atob(i))}function H(i){let e="";for(let t of g(i))t>=33&&t<=126&&t!==43&&t!==61?e+=String.fromCharCode(t):e+=`+${t.toString(16).toUpperCase().padStart(2,"0")}`;return e}function ye(i){return i.includes(";")?i:`rfc822;${i}`}var x=class{constructor(e,t,r="SmtpMailer"){this.connector=t;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=le,this.startTls=e.startTls===void 0?!0:e.startTls,this.credentials=e.credentials,this.pipelining=e.pipelining===!1?!1:"auto",this.dsn=e.dsn||{},this.dkim=e.dkim,O(this.dkim),this.socketTimeoutMs=e.socketTimeoutMs||6e4,this.responseTimeoutMs=e.responseTimeoutMs||3e4,this.logger=new A(e.logLevel,`[${r}:${this.host}:${this.port}]`)}connector;host;port;secure;startTls;authType;credentials;pipelining;socketTimeoutMs;responseTimeoutMs;socket;reader;writer;responseBuffer="";logger;dsn;dkim;active=!1;closeError;sendChain=Promise.resolve();emailSending=null;queuedSendRejects=new Set;supportsDSN=!1;supportsSize=!1;maxMessageSize;supports8BitMime=!1;supportsSmtpUtf8=!1;supportsRequireTls=!1;allowAuth=!1;authTypeSupported=[];supportsStartTls=!1;supportsPipelining=!1;send(e){let t=new S(e);t.sent.catch(()=>{});let r,s=!1,n=new Promise((a,o)=>{r=p=>{s=!0,o(p)},this.queuedSendRejects.add(r),this.sendChain.then(async()=>{if(this.queuedSendRejects.delete(r),s)throw this.closedSendError();if(!this.active)throw this.closedSendError();return await this.sendEmail(t)}).then(a,o)});return this.sendChain=n.catch(()=>{}),n}async sendMany(e,t={}){let r=[];for(let s of e)try{let n=await this.send(s);r.push({status:"fulfilled",value:n})}catch(n){if(!t.continueOnError)throw n;r.push({status:"rejected",reason:n})}return r}async close(e){let t=e||new c("EdgeMailer is shutting down",{stage:"quit"});this.active=!1,this.closeError=t,this.logger.info("EdgeMailer is closed",e?.message||"");for(let r of this.queuedSendRejects)r(this.closedSendError());if(this.queuedSendRejects.clear(),!e)try{await this.writeLine("QUIT"),await this.readTimeout("quit","QUIT")}catch{}await this.closeSocket()}isActive(){return this.active}async initializeSmtpSession(){await this.openSocket(),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}async sendEmail(e){this.emailSending=e;let t=await this.prepareEmail(e),r={accepted:[],rejected:[]};try{this.canPipeline()?r=await this.envelopePipelined(t):(await this.mail(t),r=await this.rcpt(t),await this.data());let s=await this.body(t);return e.setSent(),this.createReceipt(t,r,s)}catch(s){let n=s instanceof Error?s:new c("Failed to send email",{stage:"send",cause:s});if(this.logger.error("Failed to send email: "+n.message),e.setSentError(n),this.active)try{await this.rset()}catch(a){await this.close(a instanceof Error?a:new c("Failed to reset after send error",{stage:"rset",cause:a}))}throw n}finally{this.emailSending=null}}canPipeline(){return this.pipelining==="auto"&&this.supportsPipelining}async prepareEmail(e){let t=await e.getMessageDataAsync(),r=this.dkim?await B(t,this.dkim):t,s=S.toSmtpData(r);return{email:e,data:s,size:Math.max(0,g(s).length-g(`.\r
|
|
64
|
+
`).length)}}async readTimeout(e,t){let r,s=!1,n=new c("Timeout while waiting for smtp server response",{stage:e,command:t}),a=this.read(e,t);a.catch(()=>{});try{return await Promise.race([a,new Promise((o,d)=>{r=setTimeout(()=>{s=!0,this.abortConnection(n),d(n)},this.responseTimeoutMs)})])}finally{r&&clearTimeout(r),s&&(this.closeError=n)}}async read(e,t){let r=this.shiftResponse();if(r)return r;for(;;){let{value:s,done:n}=await this.reader.read();if(n)throw new c("SMTP server closed the connection",{stage:e,command:t,response:this.responseBuffer});if(!s?.length)continue;let a=P(s).toString();this.logger.debug(`SMTP server response:
|
|
65
|
+
`+a),this.responseBuffer=this.responseBuffer+a;let o=this.shiftResponse();if(o)return o}}shiftResponse(){let e=0,t=/\r?\n/g,r;for(;r=t.exec(this.responseBuffer);){let s=this.responseBuffer.slice(e,r.index),n=r.index+r[0].length;if(/^\d{3}(?:\s|$)/.test(s)){let a=this.responseBuffer.slice(0,n);return this.responseBuffer=this.responseBuffer.slice(n),a}e=n}}async writeLine(e){await this.write(`${e}\r
|
|
66
|
+
`)}async write(e){this.logger.debug(`Write to socket:
|
|
67
|
+
`+e),await this.writer.write(g(e))}async openSocket(){this.logger.info("Connecting to SMTP server");let e=new c("Socket timeout!",{stage:"connect"}),t=new AbortController,r,s=!1;try{let n=await Promise.race([Promise.resolve(this.connector.connect({hostname:this.host,port:this.port,tls:this.secure?"on":this.startTls?"starttls":"off",signal:t.signal})),new Promise((a,o)=>{r=setTimeout(()=>{s=!0,t.abort(e),o(e)},this.socketTimeoutMs)})]);this.socket=n,this.reader=n.readable.getReader(),this.writer=n.writable.getWriter()}finally{r&&clearTimeout(r),s&&(this.closeError=e)}}async waitForSocketConnected(){let e=this.getSocket();if(!e.opened){this.logger.info("SMTP server connected");return}let t=new c("Socket timeout!",{stage:"connect"}),r;try{await Promise.race([e.opened,new Promise((s,n)=>{r=setTimeout(()=>{this.abortConnection(t),n(t)},this.socketTimeoutMs)})])}finally{r&&clearTimeout(r)}this.logger.info("SMTP server connected")}async greet(){let e=await this.readTimeout("greet");if(!e.startsWith("220"))throw new c("Failed to connect to SMTP server: "+e,{stage:"greet",response:e})}async ehlo(){let e="EHLO 127.0.0.1";await this.writeLine(e);let t=await this.readTimeout("ehlo",e);if(t.startsWith("421"))throw new c(`Failed to EHLO. ${t}`,{stage:"ehlo",command:e,response:t});if(!t.startsWith("2")){await this.helo();return}this.parseCapabilities(t)}async helo(){let e="HELO 127.0.0.1";await this.writeLine(e);let t=await this.readTimeout("helo",e);if(!t.startsWith("2"))throw new c(`Failed to HELO. ${t}`,{stage:"helo",command:e,response:t})}async tls(){let e="STARTTLS";await this.writeLine(e);let t=await this.readTimeout("starttls",e);if(!t.startsWith("220"))throw new c("Failed to start TLS: "+t,{stage:"starttls",command:e,response:t});this.reader.releaseLock(),this.writer.releaseLock();let r=this.getSocket();if(!r.startTls)throw new c("Runtime socket does not support STARTTLS",{stage:"starttls",command:e,response:t});this.socket=await r.startTls(),this.reader=this.socket.readable.getReader(),this.writer=this.socket.writable.getWriter(),this.resetCapabilities()}resetCapabilities(){this.supportsDSN=!1,this.supportsSize=!1,this.maxMessageSize=void 0,this.supports8BitMime=!1,this.supportsSmtpUtf8=!1,this.supportsRequireTls=!1,this.allowAuth=!1,this.authTypeSupported=[],this.supportsStartTls=!1,this.supportsPipelining=!1}parseCapabilities(e){this.resetCapabilities();for(let t of e.split(/\r?\n/)){let r=t.match(/^250[ -]([A-Z0-9][A-Z0-9-]*)(?:[ =](.*))?$/i);if(!r)continue;let s=r[1].toUpperCase(),n=r[2]?.trim()||"";if(s==="AUTH"){this.allowAuth=!0;for(let a of n.split(/\s+/)){let o=a.toLowerCase();(o==="plain"||o==="login"||o==="cram-md5")&&this.authTypeSupported.push(o)}}else s==="STARTTLS"?this.supportsStartTls=!0:s==="DSN"?this.supportsDSN=!0:s==="PIPELINING"?this.supportsPipelining=!0:s==="SIZE"?(this.supportsSize=!0,this.maxMessageSize=n?Number(n):void 0):s==="8BITMIME"?this.supports8BitMime=!0:s==="SMTPUTF8"?this.supportsSmtpUtf8=!0:s==="REQUIRETLS"&&(this.supportsRequireTls=!0)}}async auth(){if(this.allowAuth&&this.credentials)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 c("No supported auth method found.",{stage:"auth"})}async authWithPlain(){let e=`AUTH PLAIN ${btoa(`\0${this.credentials.username}\0${this.credentials.password}`)}`;await this.writeLine(e);let t=await this.readTimeout("auth","AUTH PLAIN");if(!t.startsWith("2"))throw new c(`Failed to plain authentication: ${t}`,{stage:"auth",command:"AUTH PLAIN",response:t})}async authWithLogin(){await this.writeLine("AUTH LOGIN");let e=await this.readTimeout("auth","AUTH LOGIN");if(!e.startsWith("3"))throw new c("Invalid login: "+e,{stage:"auth",command:"AUTH LOGIN",response:e});let t=btoa(this.credentials.username);await this.writeLine(t);let r=await this.readTimeout("auth","AUTH LOGIN username");if(!r.startsWith("3"))throw new c("Failed to login authentication: "+r,{stage:"auth",command:"AUTH LOGIN username",response:r});let s=btoa(this.credentials.password);await this.writeLine(s);let n=await this.readTimeout("auth","AUTH LOGIN password");if(!n.startsWith("2"))throw new c("Failed to login authentication: "+n,{stage:"auth",command:"AUTH LOGIN password",response:n})}async authWithCramMD5(){let e="AUTH CRAM-MD5";await this.writeLine(e);let t=await this.readTimeout("auth",e),r=t.match(/^334\s+([^\r\n]+)/)?.pop();if(!r)throw new c("Invalid CRAM-MD5 challenge: "+t,{stage:"auth",command:e,response:t});let s;try{s=ge(r)}catch(o){throw new c("Invalid CRAM-MD5 challenge encoding",{stage:"auth",command:e,response:t,cause:o})}let n=me(g(this.credentials.password),s);await this.writeLine(btoa(`${this.credentials.username} ${n}`));let a=await this.readTimeout("auth",e);if(!a.startsWith("2"))throw new c("Failed to cram-md5 authentication: "+a,{stage:"auth",command:e,response:a})}async mail(e){let t=this.mailCommand(e);await this.writeLine(t);let r=await this.readTimeout("mail",t);if(!r.startsWith("2"))throw new c(`Invalid ${t} ${r}`,{stage:"mail",command:t,response:r})}async rcpt(e){let t={accepted:[],rejected:[]},r=this.recipients(e.email);for(let s of r){let n=this.rcptCommand(s,e.email);await this.writeLine(n);let a=await this.readTimeout("rcpt",n);if(!a.startsWith("2"))throw t.rejected.push(this.rejectedRecipient(s,a)),new c(`Invalid ${n} ${a}`,{stage:"rcpt",command:n,response:a});t.accepted.push(s)}return t}async data(){let e="DATA";await this.writeLine(e);let t=await this.readTimeout("data",e);if(!t.startsWith("3"))throw new c(`Failed to send DATA: ${t}`,{stage:"data",command:e,response:t})}async envelopePipelined(e){let t={accepted:[],rejected:[]},r=this.mailCommand(e),s=this.recipients(e.email),n=s.map(l=>this.rcptCommand(l,e.email)),a="DATA";await this.writeLine(r);for(let l of n)await this.writeLine(l);await this.writeLine(a);let o,d=await this.readTimeout("mail",r);d.startsWith("2")||(o=new c(`Invalid ${r} ${d}`,{stage:"mail",command:r,response:d}));for(let[l,f]of n.entries()){let h=await this.readTimeout("rcpt",f);h.startsWith("2")?h.startsWith("2")&&t.accepted.push(s[l]):(t.rejected.push(this.rejectedRecipient(s[l],h)),o||(o=new c(`Invalid ${f} ${h}`,{stage:"rcpt",command:f,response:h})))}let p=await this.readTimeout("data",a);if(!p.startsWith("3")&&!o&&(o=new c(`Failed to send DATA: ${p}`,{stage:"data",command:a,response:p})),o)throw o;return t}async body(e){await this.write(e.data);let t=await this.readTimeout("body","<message body>");if(!t.startsWith("2"))throw new c("Failed send email body: "+t,{stage:"body",command:"<message body>",response:t});return t}createReceipt(e,t,r){return{messageId:e.email.headers["Message-ID"]||"",envelope:{from:this.mailFrom(e.email),to:this.recipients(e.email)},accepted:t.accepted,rejected:t.rejected,response:r,responseCode:this.responseCode(r),enhancedStatusCode:this.enhancedStatusCode(r),size:e.size}}rejectedRecipient(e,t){let r=this.responseCode(t);return{recipient:e,response:t,responseCode:r,enhancedStatusCode:this.enhancedStatusCode(t),transient:r?r>=400&&r<500:!1}}responseCode(e){let t=e.match(/^(\d{3})/);return t?Number(t[1]):void 0}enhancedStatusCode(e){return e.match(/^\d{3}[ -]([245]\.\d{1,3}\.\d{1,3})\b/)?.at(1)}async rset(){let e="RSET";await this.writeLine(e);let t=await this.readTimeout("rset",e);if(!t.startsWith("2"))throw new c(`Failed to reset: ${t}`,{stage:"rset",command:e,response:t})}mailCommand(e){let t=e.email,r=[`MAIL FROM: <${this.mailFrom(t)}>`],s=this.mailParameters(e);return s.length&&r.push(s.join(" ")),r.join(" ")}rcptCommand(e,t){let r=`RCPT TO: <${e}>`,s=this.rcptParameters(t);return s.length&&(r+=` ${s.join(" ")}`),r}recipients(e){return e.envelope?.to||[...e.to.map(t=>t.email),...(e.cc||[]).map(t=>t.email),...(e.bcc||[]).map(t=>t.email)]}mailFrom(e){return e.envelope?.from||e.from.email}mailParameters(e){let t=e.email,r=[],s=t.envelope;this.supportsSize&&r.push(`SIZE=${s?.size??e.size}`);let n=s?.body?.toUpperCase();if(n){if(!this.supports8BitMime)throw new c(`${n} requires 8BITMIME support`,{stage:"mail",command:"MAIL FROM"});r.push(`BODY=${n}`)}if(this.needsSmtpUtf8(t)){if(!this.supportsSmtpUtf8)throw new c("SMTPUTF8 is not supported by the SMTP server",{stage:"mail",command:"MAIL FROM"});r.push("SMTPUTF8")}if(s?.requireTls){if(!this.supportsRequireTls)throw new c("REQUIRETLS is not supported by the SMTP server",{stage:"mail",command:"MAIL FROM"});r.push("REQUIRETLS")}if(this.supportsDSN&&this.hasDsnRequest(t)){let a=this.retParameter(t);a&&r.push(a);let o=t.dsnOverride?.envelopeId||this.dsn?.envelopeId;o&&r.push(`ENVID=${H(o)}`)}return r}rcptParameters(e){if(!this.supportsDSN||!this.hasDsnRequest(e))return[];let t=[this.notificationParameter(e)],r=e.dsnOverride?.ORCPT||this.dsn?.ORCPT;return r&&t.push(`ORCPT=${H(ye(r))}`),t}notificationParameter(e){if(e.dsnOverride?.NOTIFY?.NEVER||this.dsn?.NOTIFY?.NEVER)return"NOTIFY=NEVER";let t=[];return(e.dsnOverride?.NOTIFY&&e.dsnOverride.NOTIFY.SUCCESS||!e.dsnOverride?.NOTIFY&&this.dsn?.NOTIFY?.SUCCESS)&&t.push("SUCCESS"),(e.dsnOverride?.NOTIFY&&e.dsnOverride.NOTIFY.FAILURE||!e.dsnOverride?.NOTIFY&&this.dsn?.NOTIFY?.FAILURE)&&t.push("FAILURE"),(e.dsnOverride?.NOTIFY&&e.dsnOverride.NOTIFY.DELAY||!e.dsnOverride?.NOTIFY&&this.dsn?.NOTIFY?.DELAY)&&t.push("DELAY"),t.length>0?`NOTIFY=${t.join(",")}`:"NOTIFY=NEVER"}retParameter(e){if(e.dsnOverride?.RET&&e.dsnOverride.RET.FULL||!e.dsnOverride?.RET&&this.dsn?.RET?.FULL)return"RET=FULL";if(e.dsnOverride?.RET&&e.dsnOverride.RET.HEADERS||!e.dsnOverride?.RET&&this.dsn?.RET?.HEADERS)return"RET=HDRS"}hasDsnRequest(e){return!!(e.dsnOverride?.envelopeId||e.dsnOverride?.RET||e.dsnOverride?.NOTIFY||e.dsnOverride?.ORCPT||this.dsn?.envelopeId||this.dsn?.RET||this.dsn?.NOTIFY||this.dsn?.ORCPT)}needsSmtpUtf8(e){return!!e.envelope?.smtpUtf8||[this.mailFrom(e),...this.recipients(e)].some(t=>/[^\x00-\x7F]/.test(t))}async abortConnection(e){this.active=!1,this.closeError=e instanceof Error?e:new c("SMTP connection aborted",{stage:"send",cause:e});for(let t of this.queuedSendRejects)t(this.closedSendError());this.queuedSendRejects.clear(),await this.closeSocket()}closedSendError(){return new c(this.closeError?.message||"EdgeMailer is closed",{stage:"send",cause:this.closeError})}async closeSocket(){let e=this.socket;if(e)try{await e.close()}catch{this.logger.error("Failed to close socket")}}getSocket(){if(!this.socket)throw new c("SMTP socket is not open",{stage:"connect"});return this.socket}};var C=class{constructor(e,t){this.options=e;this.connectMailer=t;let r=we(e.pool);this.maxConnections=Math.max(1,r.maxConnections??1),this.maxMessagesPerConnection=Math.max(1,r.maxMessagesPerConnection??Number.MAX_SAFE_INTEGER),this.idleTimeoutMs=Math.max(0,r.idleTimeoutMs??6e4)}options;connectMailer;maxConnections;maxMessagesPerConnection;idleTimeoutMs;ready=[];busy=new Set;waitQueue=[];pendingCreates=new Set;pendingDestroys=new Set;totalConnections=0;closed=!1;async send(e){let t=await this.acquire();try{let r=await t.mailer.send(e);return t.messages++,await this.release(t),r}catch(r){throw t.messages++,await this.release(t),r}}async sendMany(e,t={}){let r=[];for(let s of e)try{r.push({status:"fulfilled",value:await this.send(s)})}catch(n){if(!t.continueOnError)throw n;r.push({status:"rejected",reason:n})}return r}async close(){this.closed=!0;for(let t of this.waitQueue)t.reject(new Error("SMTP connection pool is closed"));this.waitQueue=[];let e=[...this.ready,...this.busy];this.ready=[],this.busy.clear();for(let t of e)this.trackDestroy(t,!1);await this.waitForDrained()}async acquire(){if(this.closed)throw new Error("SMTP connection pool is closed");let e=this.ready.shift();return e?(this.clearIdleTimer(e),this.busy.add(e),e):this.totalConnections<this.maxConnections?await this.createBusyClient():await new Promise((t,r)=>{this.waitQueue.push({resolve:t,reject:r})})}async release(e){if(e.destroyPromise){await this.trackDestroy(e);return}if(this.busy.delete(e),this.closed||!e.mailer.isActive()||e.messages>=this.maxMessagesPerConnection){await this.trackDestroy(e);return}let t=this.waitQueue.shift();if(t){this.busy.add(e),t.resolve(e);return}this.ready.push(e),this.idleTimeoutMs>0&&(e.idleTimer=setTimeout(()=>{this.ready=this.ready.filter(r=>r!==e),this.trackDestroy(e)},this.idleTimeoutMs))}dispatchWaiters(){if(!this.closed){for(;this.waitQueue.length&&this.ready.length;){let e=this.waitQueue.shift(),t=this.ready.shift();this.clearIdleTimer(t),this.busy.add(t),e.resolve(t)}for(;this.waitQueue.length&&this.totalConnections<this.maxConnections;){let e=this.waitQueue.shift();this.createBusyClient().then(e.resolve,e.reject)}}}createBusyClient(){let e=this.createBusyClientInner(),t=e.then(()=>{},()=>{});return this.pendingCreates.add(t),t.finally(()=>{this.pendingCreates.delete(t)}),e}async createBusyClientInner(){this.totalConnections++;let e=!1;try{let r={mailer:await this.connectMailer(this.options),messages:0};if(this.closed)throw e=!0,await this.trackDestroy(r,!1),new Error("SMTP connection pool is closed");return this.busy.add(r),r}catch(t){throw e||(this.totalConnections--,this.dispatchWaiters()),t}}trackDestroy(e,t=!0){let r=this.destroy(e);return this.pendingDestroys.add(r),r.finally(()=>{this.pendingDestroys.delete(r),t&&this.dispatchWaiters()}),r}destroy(e){return e.destroyPromise||(e.destroyPromise=this.destroyOnce(e)),e.destroyPromise}async destroyOnce(e){this.clearIdleTimer(e),this.ready=this.ready.filter(t=>t!==e),this.busy.delete(e);try{await e.mailer.close()}catch{}this.totalConnections=Math.max(0,this.totalConnections-1)}async waitForDrained(){for(;this.pendingCreates.size||this.pendingDestroys.size;)await Promise.all([...this.pendingCreates,...this.pendingDestroys])}clearIdleTimer(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=void 0)}};function we(i){return i&&typeof i=="object"?i:{}}function Te(i){return i==="on"?"on":i==="starttls"?"starttls":"off"}var z={connect(i){return(0,q.connect)({hostname:i.hostname,port:i.port},{secureTransport:Te(i.tls),allowHalfOpen:!1})}},L=class i extends x{constructor(e){super(e,z,"EdgeMailer")}static async connect(e){let t=new i(e);try{return await t.initializeSmtpSession(),t}catch(r){throw await t.abortConnection(r),r}}static async send(e,t){let r=await i.connect(e);try{return await r.send(t)}finally{await r.close()}}static async sendBatch(e,t,r={}){let s=await i.connect(e);try{return await s.sendMany(t,r)}finally{await s.close()}}static createPool(e){return new C(e,i.connect)}};0&&(module.exports={EdgeMailer,Email,LogLevel,SMTPError,SmtpConnectionPool,SmtpMailer,cloudflareSocketConnector,encodeHeader,signDkimMessage,validateDkimConfig});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as L,b as g}from"./chunk-MKG2NZOU.mjs";import{a as r,b as e,c as f,d as m,e as o,f as p,g as t,h as x}from"./chunk-U6ERJXK6.mjs";export{g as EdgeMailer,e as Email,o as LogLevel,p as SMTPError,x as SmtpConnectionPool,t as SmtpMailer,L as cloudflareSocketConnector,r as encodeHeader,m as signDkimMessage,f as validateDkimConfig};
|
package/dist/deno.d.mts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { o as SmtpMailer, E as EdgeMailerOptions, i as EmailOptions, r as SmtpSendReceipt, B as BatchSendOptions, c as BatchSendResult, n as SmtpConnectionPool, e as EdgeSocketConnector } from './pool-CcxclJQJ.mjs';
|
|
2
|
+
export { A as AttachmentDisposition, a as AttachmentEncoding, b as AuthType, C as Credentials, D as DkimConfig, d as DsnOptions, f as Email, g as EmailAttachment, h as EmailAttachmentContent, L as LogLevel, M as MailBodyType, j as MailEnvelopeOptions, P as PipeliningMode, S as SMTPError, k as SMTPErrorOptions, l as SMTPStage, m as SmtpBodyType, p as SmtpPoolOptions, q as SmtpRejectedRecipient, U as User, s as encodeHeader, t as signDkimMessage, v as validateDkimConfig } from './pool-CcxclJQJ.mjs';
|
|
3
|
+
|
|
4
|
+
type DenoConn = {
|
|
5
|
+
readable: ReadableStream<Uint8Array>;
|
|
6
|
+
writable: WritableStream<Uint8Array>;
|
|
7
|
+
closed?: Promise<void>;
|
|
8
|
+
close(): void;
|
|
9
|
+
};
|
|
10
|
+
type DenoSocketRuntime = {
|
|
11
|
+
connect(options: {
|
|
12
|
+
hostname: string;
|
|
13
|
+
port: number;
|
|
14
|
+
transport?: 'tcp';
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
}): Promise<DenoConn>;
|
|
17
|
+
connectTls(options: {
|
|
18
|
+
hostname: string;
|
|
19
|
+
port: number;
|
|
20
|
+
}): Promise<DenoConn>;
|
|
21
|
+
startTls(conn: DenoConn, options?: {
|
|
22
|
+
hostname?: string;
|
|
23
|
+
}): Promise<DenoConn>;
|
|
24
|
+
};
|
|
25
|
+
declare function createDenoSocketConnector(deno?: DenoSocketRuntime): EdgeSocketConnector;
|
|
26
|
+
declare class DenoMailer extends SmtpMailer {
|
|
27
|
+
private constructor();
|
|
28
|
+
static connect(options: EdgeMailerOptions): Promise<DenoMailer>;
|
|
29
|
+
static send(options: EdgeMailerOptions, email: EmailOptions): Promise<SmtpSendReceipt>;
|
|
30
|
+
static sendBatch(options: EdgeMailerOptions, emails: EmailOptions[], batchOptions?: BatchSendOptions): Promise<BatchSendResult>;
|
|
31
|
+
static createPool(options: EdgeMailerOptions): SmtpConnectionPool<DenoMailer>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { BatchSendOptions, BatchSendResult, DenoMailer, type DenoSocketRuntime, EdgeMailerOptions, EmailOptions, SmtpConnectionPool, SmtpMailer, SmtpSendReceipt, createDenoSocketConnector };
|
package/dist/deno.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { o as SmtpMailer, E as EdgeMailerOptions, i as EmailOptions, r as SmtpSendReceipt, B as BatchSendOptions, c as BatchSendResult, n as SmtpConnectionPool, e as EdgeSocketConnector } from './pool-CcxclJQJ.js';
|
|
2
|
+
export { A as AttachmentDisposition, a as AttachmentEncoding, b as AuthType, C as Credentials, D as DkimConfig, d as DsnOptions, f as Email, g as EmailAttachment, h as EmailAttachmentContent, L as LogLevel, M as MailBodyType, j as MailEnvelopeOptions, P as PipeliningMode, S as SMTPError, k as SMTPErrorOptions, l as SMTPStage, m as SmtpBodyType, p as SmtpPoolOptions, q as SmtpRejectedRecipient, U as User, s as encodeHeader, t as signDkimMessage, v as validateDkimConfig } from './pool-CcxclJQJ.js';
|
|
3
|
+
|
|
4
|
+
type DenoConn = {
|
|
5
|
+
readable: ReadableStream<Uint8Array>;
|
|
6
|
+
writable: WritableStream<Uint8Array>;
|
|
7
|
+
closed?: Promise<void>;
|
|
8
|
+
close(): void;
|
|
9
|
+
};
|
|
10
|
+
type DenoSocketRuntime = {
|
|
11
|
+
connect(options: {
|
|
12
|
+
hostname: string;
|
|
13
|
+
port: number;
|
|
14
|
+
transport?: 'tcp';
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
}): Promise<DenoConn>;
|
|
17
|
+
connectTls(options: {
|
|
18
|
+
hostname: string;
|
|
19
|
+
port: number;
|
|
20
|
+
}): Promise<DenoConn>;
|
|
21
|
+
startTls(conn: DenoConn, options?: {
|
|
22
|
+
hostname?: string;
|
|
23
|
+
}): Promise<DenoConn>;
|
|
24
|
+
};
|
|
25
|
+
declare function createDenoSocketConnector(deno?: DenoSocketRuntime): EdgeSocketConnector;
|
|
26
|
+
declare class DenoMailer extends SmtpMailer {
|
|
27
|
+
private constructor();
|
|
28
|
+
static connect(options: EdgeMailerOptions): Promise<DenoMailer>;
|
|
29
|
+
static send(options: EdgeMailerOptions, email: EmailOptions): Promise<SmtpSendReceipt>;
|
|
30
|
+
static sendBatch(options: EdgeMailerOptions, emails: EmailOptions[], batchOptions?: BatchSendOptions): Promise<BatchSendResult>;
|
|
31
|
+
static createPool(options: EdgeMailerOptions): SmtpConnectionPool<DenoMailer>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export { BatchSendOptions, BatchSendResult, DenoMailer, type DenoSocketRuntime, EdgeMailerOptions, EmailOptions, SmtpConnectionPool, SmtpMailer, SmtpSendReceipt, createDenoSocketConnector };
|
package/dist/deno.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";var O=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var Q=Object.prototype.hasOwnProperty;var _=(i,e)=>{for(var t in e)O(i,t,{get:e[t],enumerable:!0})},G=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of V(e))!Q.call(i,s)&&s!==t&&O(i,s,{get:()=>e[s],enumerable:!(r=K(e,s))||r.enumerable});return i};var Z=i=>G(O({},"__esModule",{value:!0}),i);var ve={};_(ve,{DenoMailer:()=>F,Email:()=>S,LogLevel:()=>k,SMTPError:()=>c,SmtpConnectionPool:()=>P,SmtpMailer:()=>C,createDenoSocketConnector:()=>z,encodeHeader:()=>v,signDkimMessage:()=>B,validateDkimConfig:()=>D});module.exports=Z(ve);var X=new TextEncoder;function g(i){return X.encode(i)}var J=new TextDecoder("utf-8");function x(i){return J.decode(i)}function R(i,e=76){let t=g(i),r="",s=0,n=0;for(;n<t.length;){let a=t[n],o;if(a===10){r+=`\r
|
|
2
|
+
`,s=0,n++;continue}else if(a===13)if(n+1<t.length&&t[n+1]===10){r+=`\r
|
|
3
|
+
`,s=0,n+=2;continue}else o="=0D";if(o===void 0){let d=a===32||a===9,p=n+1>=t.length||t[n+1]===10||t[n+1]===13;a<32&&!d||a>126||a===61||d&&p?o=`=${a.toString(16).toUpperCase().padStart(2,"0")}`:o=String.fromCharCode(a)}s+o.length>e-3&&(r+=`=\r
|
|
4
|
+
`,s=0),r+=o,s+=o.length,n++}return r}function v(i){if(!/[^\x00-\x7F]/.test(i))return i;let e=g(i),t="";for(let r of e)r>=33&&r<=126&&r!==63&&r!==61&&r!==95?t+=String.fromCharCode(r):r===32?t+="_":t+=`=${r.toString(16).toUpperCase().padStart(2,"0")}`;return`=?UTF-8?Q?${t}?=`}var S=class i{from;to;reply;cc;bcc;subject;text;html;envelope;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 Error("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=i.toUsers(e.to),this.cc=i.toUsers(e.cc),this.bcc=i.toUsers(e.bcc),this.subject=e.subject,this.text=e.text,this.html=e.html,this.attachments=e.attachments,this.envelope=e.envelope?{...e.envelope,to:i.toEnvelopeRecipients(e.envelope.to)}:void 0,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]}static toEnvelopeRecipients(e){if(e)return Array.isArray(e)?e:[e]}getMessageData(){return this.buildMessageData(this.resolveAttachmentsSync())}async getMessageDataAsync(){return this.buildMessageData(await this.resolveAttachments())}buildMessageData(e){this.resolveHeader();let t=["MIME-Version: 1.0"];for(let[l,f]of Object.entries(this.headers))l.toLowerCase()!=="bcc"&&t.push(`${l}: ${f}`);let r=this.generateSafeBoundary("mixed_"),s=this.generateSafeBoundary("alternative_");t.push(`Content-Type: multipart/mixed; boundary="${r}"`);let a=`${t.join(`\r
|
|
5
|
+
`)}\r
|
|
6
|
+
\r
|
|
7
|
+
`,o=(e||[]).filter(l=>this.attachmentDisposition(l)==="inline"),d=(e||[]).filter(l=>this.attachmentDisposition(l)!=="inline"),p=this.generateSafeBoundary("related_");if(o.length?(a+=`--${r}\r
|
|
8
|
+
`,a+=`Content-Type: multipart/related; boundary="${p}"\r
|
|
9
|
+
\r
|
|
10
|
+
`,a+=`--${p}\r
|
|
11
|
+
`,a+=`Content-Type: multipart/alternative; boundary="${s}"\r
|
|
12
|
+
\r
|
|
13
|
+
`):(a+=`--${r}\r
|
|
14
|
+
`,a+=`Content-Type: multipart/alternative; boundary="${s}"\r
|
|
15
|
+
\r
|
|
16
|
+
`),this.text){a+=`--${s}\r
|
|
17
|
+
`,a+=`Content-Type: text/plain; charset="UTF-8"\r
|
|
18
|
+
`,a+=`Content-Transfer-Encoding: quoted-printable\r
|
|
19
|
+
\r
|
|
20
|
+
`;let l=R(this.text);a+=`${l}\r
|
|
21
|
+
\r
|
|
22
|
+
`}if(this.html){a+=`--${s}\r
|
|
23
|
+
`,a+=`Content-Type: text/html; charset="UTF-8"\r
|
|
24
|
+
`,a+=`Content-Transfer-Encoding: quoted-printable\r
|
|
25
|
+
\r
|
|
26
|
+
`;let l=R(this.html);a+=`${l}\r
|
|
27
|
+
\r
|
|
28
|
+
`}if(a+=`--${s}--\r
|
|
29
|
+
`,o.length){for(let l of o)a+=this.attachmentPart(p,l);a+=`--${p}--\r
|
|
30
|
+
`}for(let l of d)a+=this.attachmentPart(r,l);return a+=`--${r}--\r
|
|
31
|
+
`,a.endsWith(`\r
|
|
32
|
+
`)?a:`${a}\r
|
|
33
|
+
`}async resolveAttachments(){if(!this.attachments?.length)return;let e=[];for(let t of this.attachments){if(typeof t.content=="string"){e.push({...t,content:t.content});continue}if(this.isBlob(t.content)){let r=t.content;e.push({...t,content:new Uint8Array(await r.arrayBuffer()),resolvedContentType:r.type||void 0});continue}e.push({...t,content:this.attachmentBytes(t.content)})}return e}resolveAttachmentsSync(){if(this.attachments?.length)return this.attachments.map(e=>{if(typeof e.content=="string")return{...e,content:e.content};if(this.isBlob(e.content))throw new Error("Blob attachment content requires async message generation; use getMessageDataAsync(), getEmailDataAsync(), or send through a mailer");return{...e,content:this.attachmentBytes(e.content)}})}attachmentBytes(e){return e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):new Uint8Array(e.buffer,e.byteOffset,e.byteLength)}isBlob(e){return typeof Blob<"u"&&e instanceof Blob}attachmentPart(e,t){let r=t.mimeType||t.contentType||t.resolvedContentType||this.getMimeType(t.filename),s=this.attachmentDisposition(t),n=t.encoding||"base64",a=`--${e}\r
|
|
34
|
+
`;return a+=`Content-Type: ${r}; name="${t.filename}"\r
|
|
35
|
+
`,a+=`Content-Description: ${t.filename}\r
|
|
36
|
+
`,t.contentId&&(a+=`Content-ID: <${t.contentId.replace(/[<>]/g,"")}>\r
|
|
37
|
+
`),a+=`Content-Disposition: ${s}; filename="${t.filename}";\r
|
|
38
|
+
`,a+=` creation-date="${new Date().toUTCString()}";\r
|
|
39
|
+
`,a+=`Content-Transfer-Encoding: ${n}\r
|
|
40
|
+
\r
|
|
41
|
+
`,a+=`${this.encodedAttachmentContent(t,n)}\r
|
|
42
|
+
\r
|
|
43
|
+
`,a}attachmentDisposition(e){return e.disposition||(e.contentId?"inline":"attachment")}encodedAttachmentContent(e,t){if(t==="base64")return typeof e.content=="string"?this.wrapBase64(e.content):this.bytesToWrappedBase64(e.content);let r=this.attachmentTextContent(e);if(t==="quoted-printable")return R(r);if(/[^\x00-\x7F]/.test(r))throw new Error("7bit attachment content must contain ASCII only");return r.replace(/\r?\n/g,`\r
|
|
44
|
+
`)}attachmentTextContent(e){return typeof e.content=="string"?e.content:x(e.content)}bytesToWrappedBase64(e){let t="";for(let r=0;r<e.length;r+=57)t&&(t+=`\r
|
|
45
|
+
`),t+=this.bytesToBase64(e.subarray(r,r+57));return t}bytesToBase64(e){let t="";for(let r=0;r<e.length;r+=32768)t+=String.fromCharCode(...e.subarray(r,r+32768));return btoa(t)}wrapBase64(e){let t="",r="";for(let s of e)/\s/.test(s)||(r+=s,r.length===76&&(t+=t?`\r
|
|
46
|
+
${r}`:r,r=""));return r&&(t+=t?`\r
|
|
47
|
+
${r}`:r),t}getEmailData(){return i.toSmtpData(this.getMessageData())}async getEmailDataAsync(){return i.toSmtpData(await this.getMessageDataAsync())}static toSmtpData(e){let t=i.applyDotStuffing(e);return t.endsWith(`\r
|
|
48
|
+
`)?`${t}.\r
|
|
49
|
+
`:`${t}\r
|
|
50
|
+
.\r
|
|
51
|
+
`}static applyDotStuffing(e){let t=e.replace(/\r\n\./g,`\r
|
|
52
|
+
..`);return t.startsWith(".")&&(t=`.${t}`),t}generateSafeBoundary(e){let t=new Uint8Array(28);crypto.getRandomValues(t);let r=Array.from(t).map(n=>n.toString(16).padStart(2,"0")).join(""),s=e+r;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.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=`"${v(this.from.name)}" <${e}>`),this.headers.From=e}resolveTo(){if(this.headers.To)return;let e=this.to.map(t=>t.name?`"${v(t.name)}" <${t.email}>`:t.email);this.headers.To=e.join(", ")}resolveSubject(){this.headers.Subject||this.subject&&(this.headers.Subject=v(this.subject))}resolveReply(){if(!this.headers["Reply-To"]&&this.reply){let e=this.reply.email;this.reply.name&&(e=`"${v(this.reply.name)}" <${e}>`),this.headers["Reply-To"]=e}}resolveCC(){if(!this.headers.CC&&this.cc){let e=this.cc.map(t=>t.name?`"${v(t.name)}" <${t.email}>`:t.email);this.headers.CC=e.join(", ")}}};var ee=["from","to","subject","date","message-id","mime-version","content-type"];function I(...i){let e=i.reduce((s,n)=>s+n.length,0),t=new Uint8Array(e),r=0;for(let s of i)t.set(s,r),r+=s.length;return t}function te(i){let e=atob(i),t=new Uint8Array(e.length);for(let r=0;r<e.length;r++)t[r]=e.charCodeAt(r);return t}function N(i){let e=i instanceof Uint8Array?i:new Uint8Array(i),t="";for(let r=0;r<e.length;r+=32768)t+=String.fromCharCode(...e.subarray(r,r+32768));return btoa(t)}function j(i){return new Uint8Array(g(i))}function re(i){if(i<128)return new Uint8Array([i]);let e=[],t=i;for(;t>0;)e.unshift(t&255),t>>=8;return new Uint8Array([128|e.length,...e])}function U(i,e){return I(new Uint8Array([i]),re(e.length),e)}function ie(i){let e=new Uint8Array([2,1,0]),t=new Uint8Array([6,9,42,134,72,134,247,13,1,1,1]),r=new Uint8Array([5,0]),s=U(48,I(t,r)),n=U(4,i);return U(48,I(e,s,n))}function se(i){let e=i.match(/-----BEGIN (PRIVATE KEY|RSA PRIVATE KEY)-----([\s\S]+?)-----END \1-----/);if(!e)throw new Error("DKIM privateKey must be a PEM private key");let t=e[1],r=te(e[2].replace(/\s+/g,""));if(t==="PRIVATE KEY")return r;if(t==="RSA PRIVATE KEY")return ie(r);throw new Error(`Unsupported DKIM private key type: ${t}`)}function W(i){return i.replace(/\r?\n/g,`\r
|
|
53
|
+
`)}function ne(i){let e=[];for(let t of W(i).split(`\r
|
|
54
|
+
`)){if(/^[ \t]/.test(t)&&e.length){let s=e[e.length-1];s.value+=`\r
|
|
55
|
+
${t}`;continue}let r=t.indexOf(":");r<1||e.push({name:t.slice(0,r),value:t.slice(r+1)})}return e}function ae(i){let e=i.name.trim().toLowerCase(),t=i.value.replace(/\r\n[ \t]+/g," ").replace(/[ \t]+/g," ").trim();return`${e}:${t}`}function oe(i){let e=W(i).split(`\r
|
|
56
|
+
`).map(t=>t.replace(/[ \t]+$/g,"").replace(/[ \t]+/g," "));for(;e.length>0&&e[e.length-1]==="";)e.pop();return e.length?`${e.join(`\r
|
|
57
|
+
`)}\r
|
|
58
|
+
`:`\r
|
|
59
|
+
`}function ce(i,e){let t=e&&e.length?e.map(n=>n.toLowerCase()):ee,r=new Set,s=[];for(let n of t)for(let a=i.length-1;a>=0;a--)if(!r.has(a)&&i[a].name.trim().toLowerCase()===n){r.add(a),s.push(i[a]);break}return s}function D(i){if(i){if(!i.domainName?.trim())throw new Error("DKIM domainName is required");if(!i.keySelector?.trim())throw new Error("DKIM keySelector is required");if(!i.privateKey?.trim())throw new Error("DKIM privateKey is required");for(let e of[i.domainName,i.keySelector])if(/[\r\n;]/.test(e))throw new Error("DKIM domainName and keySelector must be header safe")}}async function B(i,e){D(e);let t=i.indexOf(`\r
|
|
60
|
+
\r
|
|
61
|
+
`);if(t<0)throw new Error("Unable to DKIM sign message without headers and body");let r=i.slice(0,t),s=i.slice(t+4),n=ne(r),a=ce(n,e.headerFieldNames);if(!a.some(m=>m.name.toLowerCase()==="from"))throw new Error("DKIM signing requires a From header");let o=oe(s),d=await crypto.subtle.digest("SHA-256",j(o)),p=a.map(m=>m.name.trim().toLowerCase()),l=`v=1; a=rsa-sha256; c=relaxed/relaxed; d=${e.domainName}; s=${e.keySelector}; h=${p.join(":")}; bh=${N(d)}; b=`,f=[...a.map(ae),`dkim-signature:${l}`].join(`\r
|
|
62
|
+
`),h=await crypto.subtle.importKey("pkcs8",se(e.privateKey),{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["sign"]),b=await crypto.subtle.sign("RSASSA-PKCS1-v1_5",h,j(f));return`DKIM-Signature: ${l}${N(b)}\r
|
|
63
|
+
${i}`}var k=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.NONE=4]="NONE",n))(k||{}),M=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 le=["plain","login","cram-md5"],c=class extends Error{stage;command;response;responseCode;enhancedStatusCode;transient;cause;constructor(e,t){super(e),this.name="SMTPError",this.stage=t.stage,this.command=t.command,this.response=t.response,this.cause=t.cause;let r=t.response?.match(/^(\d{3})/);this.responseCode=r?Number(r[1]):void 0,this.enhancedStatusCode=t.response?.match(/^\d{3}[ -]([245]\.\d{1,3}\.\d{1,3})\b/)?.at(1),this.transient=this.responseCode?this.responseCode>=400&&this.responseCode<500:!1}},de=[7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21],pe=Array.from({length:64},(i,e)=>Math.floor(Math.abs(Math.sin(e+1))*4294967296)>>>0);function he(i,e){return(i<<e|i>>>32-e)>>>0}function H(...i){let e=i.reduce((s,n)=>s+n.length,0),t=new Uint8Array(e),r=0;for(let s of i)t.set(s,r),r+=s.length;return t}function $(i){let e=BigInt(i.length)*8n,t=i.length+1;for(;t%64!==56;)t++;let r=new Uint8Array(t+8);r.set(i),r[i.length]=128;let s=new DataView(r.buffer);for(let h=0;h<8;h++)s.setUint8(t+h,Number(e>>BigInt(h*8)&0xffn));let n=1732584193,a=4023233417,o=2562383102,d=271733878,p=new Array(16);for(let h=0;h<r.length;h+=64){for(let u=0;u<16;u++)p[u]=s.getUint32(h+u*4,!0);let b=n,m=a,w=o,y=d;for(let u=0;u<64;u++){let E,A;u<16?(E=m&w|~m&y,A=u):u<32?(E=y&m|~y&w,A=(5*u+1)%16):u<48?(E=m^w^y,A=(3*u+5)%16):(E=w^(m|~y),A=7*u%16);let Y=y;y=w,w=m,m=m+he(b+E+pe[u]+p[A]>>>0,de[u])>>>0,b=Y}n=n+b>>>0,a=a+m>>>0,o=o+w>>>0,d=d+y>>>0}let l=new Uint8Array(16),f=new DataView(l.buffer);return f.setUint32(0,n,!0),f.setUint32(4,a,!0),f.setUint32(8,o,!0),f.setUint32(12,d,!0),l}function ue(i){return Array.from(i).map(e=>e.toString(16).padStart(2,"0")).join("")}function me(i,e){let r=i.length>64?$(i):i,s=new Uint8Array(64).fill(92),n=new Uint8Array(64).fill(54);return r.forEach((a,o)=>{s[o]^=a,n[o]^=a}),ue($(H(s,$(H(n,e)))))}function fe(i){let e=new Uint8Array(i.length);for(let t=0;t<i.length;t++)e[t]=i.charCodeAt(t)&255;return e}function ge(i){return fe(atob(i))}function q(i){let e="";for(let t of g(i))t>=33&&t<=126&&t!==43&&t!==61?e+=String.fromCharCode(t):e+=`+${t.toString(16).toUpperCase().padStart(2,"0")}`;return e}function ye(i){return i.includes(";")?i:`rfc822;${i}`}var C=class{constructor(e,t,r="SmtpMailer"){this.connector=t;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=le,this.startTls=e.startTls===void 0?!0:e.startTls,this.credentials=e.credentials,this.pipelining=e.pipelining===!1?!1:"auto",this.dsn=e.dsn||{},this.dkim=e.dkim,D(this.dkim),this.socketTimeoutMs=e.socketTimeoutMs||6e4,this.responseTimeoutMs=e.responseTimeoutMs||3e4,this.logger=new M(e.logLevel,`[${r}:${this.host}:${this.port}]`)}connector;host;port;secure;startTls;authType;credentials;pipelining;socketTimeoutMs;responseTimeoutMs;socket;reader;writer;responseBuffer="";logger;dsn;dkim;active=!1;closeError;sendChain=Promise.resolve();emailSending=null;queuedSendRejects=new Set;supportsDSN=!1;supportsSize=!1;maxMessageSize;supports8BitMime=!1;supportsSmtpUtf8=!1;supportsRequireTls=!1;allowAuth=!1;authTypeSupported=[];supportsStartTls=!1;supportsPipelining=!1;send(e){let t=new S(e);t.sent.catch(()=>{});let r,s=!1,n=new Promise((a,o)=>{r=p=>{s=!0,o(p)},this.queuedSendRejects.add(r),this.sendChain.then(async()=>{if(this.queuedSendRejects.delete(r),s)throw this.closedSendError();if(!this.active)throw this.closedSendError();return await this.sendEmail(t)}).then(a,o)});return this.sendChain=n.catch(()=>{}),n}async sendMany(e,t={}){let r=[];for(let s of e)try{let n=await this.send(s);r.push({status:"fulfilled",value:n})}catch(n){if(!t.continueOnError)throw n;r.push({status:"rejected",reason:n})}return r}async close(e){let t=e||new c("EdgeMailer is shutting down",{stage:"quit"});this.active=!1,this.closeError=t,this.logger.info("EdgeMailer is closed",e?.message||"");for(let r of this.queuedSendRejects)r(this.closedSendError());if(this.queuedSendRejects.clear(),!e)try{await this.writeLine("QUIT"),await this.readTimeout("quit","QUIT")}catch{}await this.closeSocket()}isActive(){return this.active}async initializeSmtpSession(){await this.openSocket(),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}async sendEmail(e){this.emailSending=e;let t=await this.prepareEmail(e),r={accepted:[],rejected:[]};try{this.canPipeline()?r=await this.envelopePipelined(t):(await this.mail(t),r=await this.rcpt(t),await this.data());let s=await this.body(t);return e.setSent(),this.createReceipt(t,r,s)}catch(s){let n=s instanceof Error?s:new c("Failed to send email",{stage:"send",cause:s});if(this.logger.error("Failed to send email: "+n.message),e.setSentError(n),this.active)try{await this.rset()}catch(a){await this.close(a instanceof Error?a:new c("Failed to reset after send error",{stage:"rset",cause:a}))}throw n}finally{this.emailSending=null}}canPipeline(){return this.pipelining==="auto"&&this.supportsPipelining}async prepareEmail(e){let t=await e.getMessageDataAsync(),r=this.dkim?await B(t,this.dkim):t,s=S.toSmtpData(r);return{email:e,data:s,size:Math.max(0,g(s).length-g(`.\r
|
|
64
|
+
`).length)}}async readTimeout(e,t){let r,s=!1,n=new c("Timeout while waiting for smtp server response",{stage:e,command:t}),a=this.read(e,t);a.catch(()=>{});try{return await Promise.race([a,new Promise((o,d)=>{r=setTimeout(()=>{s=!0,this.abortConnection(n),d(n)},this.responseTimeoutMs)})])}finally{r&&clearTimeout(r),s&&(this.closeError=n)}}async read(e,t){let r=this.shiftResponse();if(r)return r;for(;;){let{value:s,done:n}=await this.reader.read();if(n)throw new c("SMTP server closed the connection",{stage:e,command:t,response:this.responseBuffer});if(!s?.length)continue;let a=x(s).toString();this.logger.debug(`SMTP server response:
|
|
65
|
+
`+a),this.responseBuffer=this.responseBuffer+a;let o=this.shiftResponse();if(o)return o}}shiftResponse(){let e=0,t=/\r?\n/g,r;for(;r=t.exec(this.responseBuffer);){let s=this.responseBuffer.slice(e,r.index),n=r.index+r[0].length;if(/^\d{3}(?:\s|$)/.test(s)){let a=this.responseBuffer.slice(0,n);return this.responseBuffer=this.responseBuffer.slice(n),a}e=n}}async writeLine(e){await this.write(`${e}\r
|
|
66
|
+
`)}async write(e){this.logger.debug(`Write to socket:
|
|
67
|
+
`+e),await this.writer.write(g(e))}async openSocket(){this.logger.info("Connecting to SMTP server");let e=new c("Socket timeout!",{stage:"connect"}),t=new AbortController,r,s=!1;try{let n=await Promise.race([Promise.resolve(this.connector.connect({hostname:this.host,port:this.port,tls:this.secure?"on":this.startTls?"starttls":"off",signal:t.signal})),new Promise((a,o)=>{r=setTimeout(()=>{s=!0,t.abort(e),o(e)},this.socketTimeoutMs)})]);this.socket=n,this.reader=n.readable.getReader(),this.writer=n.writable.getWriter()}finally{r&&clearTimeout(r),s&&(this.closeError=e)}}async waitForSocketConnected(){let e=this.getSocket();if(!e.opened){this.logger.info("SMTP server connected");return}let t=new c("Socket timeout!",{stage:"connect"}),r;try{await Promise.race([e.opened,new Promise((s,n)=>{r=setTimeout(()=>{this.abortConnection(t),n(t)},this.socketTimeoutMs)})])}finally{r&&clearTimeout(r)}this.logger.info("SMTP server connected")}async greet(){let e=await this.readTimeout("greet");if(!e.startsWith("220"))throw new c("Failed to connect to SMTP server: "+e,{stage:"greet",response:e})}async ehlo(){let e="EHLO 127.0.0.1";await this.writeLine(e);let t=await this.readTimeout("ehlo",e);if(t.startsWith("421"))throw new c(`Failed to EHLO. ${t}`,{stage:"ehlo",command:e,response:t});if(!t.startsWith("2")){await this.helo();return}this.parseCapabilities(t)}async helo(){let e="HELO 127.0.0.1";await this.writeLine(e);let t=await this.readTimeout("helo",e);if(!t.startsWith("2"))throw new c(`Failed to HELO. ${t}`,{stage:"helo",command:e,response:t})}async tls(){let e="STARTTLS";await this.writeLine(e);let t=await this.readTimeout("starttls",e);if(!t.startsWith("220"))throw new c("Failed to start TLS: "+t,{stage:"starttls",command:e,response:t});this.reader.releaseLock(),this.writer.releaseLock();let r=this.getSocket();if(!r.startTls)throw new c("Runtime socket does not support STARTTLS",{stage:"starttls",command:e,response:t});this.socket=await r.startTls(),this.reader=this.socket.readable.getReader(),this.writer=this.socket.writable.getWriter(),this.resetCapabilities()}resetCapabilities(){this.supportsDSN=!1,this.supportsSize=!1,this.maxMessageSize=void 0,this.supports8BitMime=!1,this.supportsSmtpUtf8=!1,this.supportsRequireTls=!1,this.allowAuth=!1,this.authTypeSupported=[],this.supportsStartTls=!1,this.supportsPipelining=!1}parseCapabilities(e){this.resetCapabilities();for(let t of e.split(/\r?\n/)){let r=t.match(/^250[ -]([A-Z0-9][A-Z0-9-]*)(?:[ =](.*))?$/i);if(!r)continue;let s=r[1].toUpperCase(),n=r[2]?.trim()||"";if(s==="AUTH"){this.allowAuth=!0;for(let a of n.split(/\s+/)){let o=a.toLowerCase();(o==="plain"||o==="login"||o==="cram-md5")&&this.authTypeSupported.push(o)}}else s==="STARTTLS"?this.supportsStartTls=!0:s==="DSN"?this.supportsDSN=!0:s==="PIPELINING"?this.supportsPipelining=!0:s==="SIZE"?(this.supportsSize=!0,this.maxMessageSize=n?Number(n):void 0):s==="8BITMIME"?this.supports8BitMime=!0:s==="SMTPUTF8"?this.supportsSmtpUtf8=!0:s==="REQUIRETLS"&&(this.supportsRequireTls=!0)}}async auth(){if(this.allowAuth&&this.credentials)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 c("No supported auth method found.",{stage:"auth"})}async authWithPlain(){let e=`AUTH PLAIN ${btoa(`\0${this.credentials.username}\0${this.credentials.password}`)}`;await this.writeLine(e);let t=await this.readTimeout("auth","AUTH PLAIN");if(!t.startsWith("2"))throw new c(`Failed to plain authentication: ${t}`,{stage:"auth",command:"AUTH PLAIN",response:t})}async authWithLogin(){await this.writeLine("AUTH LOGIN");let e=await this.readTimeout("auth","AUTH LOGIN");if(!e.startsWith("3"))throw new c("Invalid login: "+e,{stage:"auth",command:"AUTH LOGIN",response:e});let t=btoa(this.credentials.username);await this.writeLine(t);let r=await this.readTimeout("auth","AUTH LOGIN username");if(!r.startsWith("3"))throw new c("Failed to login authentication: "+r,{stage:"auth",command:"AUTH LOGIN username",response:r});let s=btoa(this.credentials.password);await this.writeLine(s);let n=await this.readTimeout("auth","AUTH LOGIN password");if(!n.startsWith("2"))throw new c("Failed to login authentication: "+n,{stage:"auth",command:"AUTH LOGIN password",response:n})}async authWithCramMD5(){let e="AUTH CRAM-MD5";await this.writeLine(e);let t=await this.readTimeout("auth",e),r=t.match(/^334\s+([^\r\n]+)/)?.pop();if(!r)throw new c("Invalid CRAM-MD5 challenge: "+t,{stage:"auth",command:e,response:t});let s;try{s=ge(r)}catch(o){throw new c("Invalid CRAM-MD5 challenge encoding",{stage:"auth",command:e,response:t,cause:o})}let n=me(g(this.credentials.password),s);await this.writeLine(btoa(`${this.credentials.username} ${n}`));let a=await this.readTimeout("auth",e);if(!a.startsWith("2"))throw new c("Failed to cram-md5 authentication: "+a,{stage:"auth",command:e,response:a})}async mail(e){let t=this.mailCommand(e);await this.writeLine(t);let r=await this.readTimeout("mail",t);if(!r.startsWith("2"))throw new c(`Invalid ${t} ${r}`,{stage:"mail",command:t,response:r})}async rcpt(e){let t={accepted:[],rejected:[]},r=this.recipients(e.email);for(let s of r){let n=this.rcptCommand(s,e.email);await this.writeLine(n);let a=await this.readTimeout("rcpt",n);if(!a.startsWith("2"))throw t.rejected.push(this.rejectedRecipient(s,a)),new c(`Invalid ${n} ${a}`,{stage:"rcpt",command:n,response:a});t.accepted.push(s)}return t}async data(){let e="DATA";await this.writeLine(e);let t=await this.readTimeout("data",e);if(!t.startsWith("3"))throw new c(`Failed to send DATA: ${t}`,{stage:"data",command:e,response:t})}async envelopePipelined(e){let t={accepted:[],rejected:[]},r=this.mailCommand(e),s=this.recipients(e.email),n=s.map(l=>this.rcptCommand(l,e.email)),a="DATA";await this.writeLine(r);for(let l of n)await this.writeLine(l);await this.writeLine(a);let o,d=await this.readTimeout("mail",r);d.startsWith("2")||(o=new c(`Invalid ${r} ${d}`,{stage:"mail",command:r,response:d}));for(let[l,f]of n.entries()){let h=await this.readTimeout("rcpt",f);h.startsWith("2")?h.startsWith("2")&&t.accepted.push(s[l]):(t.rejected.push(this.rejectedRecipient(s[l],h)),o||(o=new c(`Invalid ${f} ${h}`,{stage:"rcpt",command:f,response:h})))}let p=await this.readTimeout("data",a);if(!p.startsWith("3")&&!o&&(o=new c(`Failed to send DATA: ${p}`,{stage:"data",command:a,response:p})),o)throw o;return t}async body(e){await this.write(e.data);let t=await this.readTimeout("body","<message body>");if(!t.startsWith("2"))throw new c("Failed send email body: "+t,{stage:"body",command:"<message body>",response:t});return t}createReceipt(e,t,r){return{messageId:e.email.headers["Message-ID"]||"",envelope:{from:this.mailFrom(e.email),to:this.recipients(e.email)},accepted:t.accepted,rejected:t.rejected,response:r,responseCode:this.responseCode(r),enhancedStatusCode:this.enhancedStatusCode(r),size:e.size}}rejectedRecipient(e,t){let r=this.responseCode(t);return{recipient:e,response:t,responseCode:r,enhancedStatusCode:this.enhancedStatusCode(t),transient:r?r>=400&&r<500:!1}}responseCode(e){let t=e.match(/^(\d{3})/);return t?Number(t[1]):void 0}enhancedStatusCode(e){return e.match(/^\d{3}[ -]([245]\.\d{1,3}\.\d{1,3})\b/)?.at(1)}async rset(){let e="RSET";await this.writeLine(e);let t=await this.readTimeout("rset",e);if(!t.startsWith("2"))throw new c(`Failed to reset: ${t}`,{stage:"rset",command:e,response:t})}mailCommand(e){let t=e.email,r=[`MAIL FROM: <${this.mailFrom(t)}>`],s=this.mailParameters(e);return s.length&&r.push(s.join(" ")),r.join(" ")}rcptCommand(e,t){let r=`RCPT TO: <${e}>`,s=this.rcptParameters(t);return s.length&&(r+=` ${s.join(" ")}`),r}recipients(e){return e.envelope?.to||[...e.to.map(t=>t.email),...(e.cc||[]).map(t=>t.email),...(e.bcc||[]).map(t=>t.email)]}mailFrom(e){return e.envelope?.from||e.from.email}mailParameters(e){let t=e.email,r=[],s=t.envelope;this.supportsSize&&r.push(`SIZE=${s?.size??e.size}`);let n=s?.body?.toUpperCase();if(n){if(!this.supports8BitMime)throw new c(`${n} requires 8BITMIME support`,{stage:"mail",command:"MAIL FROM"});r.push(`BODY=${n}`)}if(this.needsSmtpUtf8(t)){if(!this.supportsSmtpUtf8)throw new c("SMTPUTF8 is not supported by the SMTP server",{stage:"mail",command:"MAIL FROM"});r.push("SMTPUTF8")}if(s?.requireTls){if(!this.supportsRequireTls)throw new c("REQUIRETLS is not supported by the SMTP server",{stage:"mail",command:"MAIL FROM"});r.push("REQUIRETLS")}if(this.supportsDSN&&this.hasDsnRequest(t)){let a=this.retParameter(t);a&&r.push(a);let o=t.dsnOverride?.envelopeId||this.dsn?.envelopeId;o&&r.push(`ENVID=${q(o)}`)}return r}rcptParameters(e){if(!this.supportsDSN||!this.hasDsnRequest(e))return[];let t=[this.notificationParameter(e)],r=e.dsnOverride?.ORCPT||this.dsn?.ORCPT;return r&&t.push(`ORCPT=${q(ye(r))}`),t}notificationParameter(e){if(e.dsnOverride?.NOTIFY?.NEVER||this.dsn?.NOTIFY?.NEVER)return"NOTIFY=NEVER";let t=[];return(e.dsnOverride?.NOTIFY&&e.dsnOverride.NOTIFY.SUCCESS||!e.dsnOverride?.NOTIFY&&this.dsn?.NOTIFY?.SUCCESS)&&t.push("SUCCESS"),(e.dsnOverride?.NOTIFY&&e.dsnOverride.NOTIFY.FAILURE||!e.dsnOverride?.NOTIFY&&this.dsn?.NOTIFY?.FAILURE)&&t.push("FAILURE"),(e.dsnOverride?.NOTIFY&&e.dsnOverride.NOTIFY.DELAY||!e.dsnOverride?.NOTIFY&&this.dsn?.NOTIFY?.DELAY)&&t.push("DELAY"),t.length>0?`NOTIFY=${t.join(",")}`:"NOTIFY=NEVER"}retParameter(e){if(e.dsnOverride?.RET&&e.dsnOverride.RET.FULL||!e.dsnOverride?.RET&&this.dsn?.RET?.FULL)return"RET=FULL";if(e.dsnOverride?.RET&&e.dsnOverride.RET.HEADERS||!e.dsnOverride?.RET&&this.dsn?.RET?.HEADERS)return"RET=HDRS"}hasDsnRequest(e){return!!(e.dsnOverride?.envelopeId||e.dsnOverride?.RET||e.dsnOverride?.NOTIFY||e.dsnOverride?.ORCPT||this.dsn?.envelopeId||this.dsn?.RET||this.dsn?.NOTIFY||this.dsn?.ORCPT)}needsSmtpUtf8(e){return!!e.envelope?.smtpUtf8||[this.mailFrom(e),...this.recipients(e)].some(t=>/[^\x00-\x7F]/.test(t))}async abortConnection(e){this.active=!1,this.closeError=e instanceof Error?e:new c("SMTP connection aborted",{stage:"send",cause:e});for(let t of this.queuedSendRejects)t(this.closedSendError());this.queuedSendRejects.clear(),await this.closeSocket()}closedSendError(){return new c(this.closeError?.message||"EdgeMailer is closed",{stage:"send",cause:this.closeError})}async closeSocket(){let e=this.socket;if(e)try{await e.close()}catch{this.logger.error("Failed to close socket")}}getSocket(){if(!this.socket)throw new c("SMTP socket is not open",{stage:"connect"});return this.socket}};var P=class{constructor(e,t){this.options=e;this.connectMailer=t;let r=we(e.pool);this.maxConnections=Math.max(1,r.maxConnections??1),this.maxMessagesPerConnection=Math.max(1,r.maxMessagesPerConnection??Number.MAX_SAFE_INTEGER),this.idleTimeoutMs=Math.max(0,r.idleTimeoutMs??6e4)}options;connectMailer;maxConnections;maxMessagesPerConnection;idleTimeoutMs;ready=[];busy=new Set;waitQueue=[];pendingCreates=new Set;pendingDestroys=new Set;totalConnections=0;closed=!1;async send(e){let t=await this.acquire();try{let r=await t.mailer.send(e);return t.messages++,await this.release(t),r}catch(r){throw t.messages++,await this.release(t),r}}async sendMany(e,t={}){let r=[];for(let s of e)try{r.push({status:"fulfilled",value:await this.send(s)})}catch(n){if(!t.continueOnError)throw n;r.push({status:"rejected",reason:n})}return r}async close(){this.closed=!0;for(let t of this.waitQueue)t.reject(new Error("SMTP connection pool is closed"));this.waitQueue=[];let e=[...this.ready,...this.busy];this.ready=[],this.busy.clear();for(let t of e)this.trackDestroy(t,!1);await this.waitForDrained()}async acquire(){if(this.closed)throw new Error("SMTP connection pool is closed");let e=this.ready.shift();return e?(this.clearIdleTimer(e),this.busy.add(e),e):this.totalConnections<this.maxConnections?await this.createBusyClient():await new Promise((t,r)=>{this.waitQueue.push({resolve:t,reject:r})})}async release(e){if(e.destroyPromise){await this.trackDestroy(e);return}if(this.busy.delete(e),this.closed||!e.mailer.isActive()||e.messages>=this.maxMessagesPerConnection){await this.trackDestroy(e);return}let t=this.waitQueue.shift();if(t){this.busy.add(e),t.resolve(e);return}this.ready.push(e),this.idleTimeoutMs>0&&(e.idleTimer=setTimeout(()=>{this.ready=this.ready.filter(r=>r!==e),this.trackDestroy(e)},this.idleTimeoutMs))}dispatchWaiters(){if(!this.closed){for(;this.waitQueue.length&&this.ready.length;){let e=this.waitQueue.shift(),t=this.ready.shift();this.clearIdleTimer(t),this.busy.add(t),e.resolve(t)}for(;this.waitQueue.length&&this.totalConnections<this.maxConnections;){let e=this.waitQueue.shift();this.createBusyClient().then(e.resolve,e.reject)}}}createBusyClient(){let e=this.createBusyClientInner(),t=e.then(()=>{},()=>{});return this.pendingCreates.add(t),t.finally(()=>{this.pendingCreates.delete(t)}),e}async createBusyClientInner(){this.totalConnections++;let e=!1;try{let r={mailer:await this.connectMailer(this.options),messages:0};if(this.closed)throw e=!0,await this.trackDestroy(r,!1),new Error("SMTP connection pool is closed");return this.busy.add(r),r}catch(t){throw e||(this.totalConnections--,this.dispatchWaiters()),t}}trackDestroy(e,t=!0){let r=this.destroy(e);return this.pendingDestroys.add(r),r.finally(()=>{this.pendingDestroys.delete(r),t&&this.dispatchWaiters()}),r}destroy(e){return e.destroyPromise||(e.destroyPromise=this.destroyOnce(e)),e.destroyPromise}async destroyOnce(e){this.clearIdleTimer(e),this.ready=this.ready.filter(t=>t!==e),this.busy.delete(e);try{await e.mailer.close()}catch{}this.totalConnections=Math.max(0,this.totalConnections-1)}async waitForDrained(){for(;this.pendingCreates.size||this.pendingDestroys.size;)await Promise.all([...this.pendingCreates,...this.pendingDestroys])}clearIdleTimer(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=void 0)}};function we(i){return i&&typeof i=="object"?i:{}}function Te(){let i=globalThis.Deno;if(!i)throw new Error("Deno runtime APIs are not available");return i}function L(i,e,t,r){return{readable:i.readable,writable:i.writable,closed:i.closed,close(){i.close()},startTls:r?async()=>L(await e.startTls(i,{hostname:t}),e,t,!1):void 0}}function z(i=Te()){return{async connect(e){if(e.tls==="on")return L(await i.connectTls({hostname:e.hostname,port:e.port}),i,e.hostname,!1);let t=await i.connect({hostname:e.hostname,port:e.port,transport:"tcp",signal:e.signal});return L(t,i,e.hostname,e.tls==="starttls")}}}var F=class i extends C{constructor(e){super(e,z(),"DenoMailer")}static async connect(e){let t=new i(e);try{return await t.initializeSmtpSession(),t}catch(r){throw await t.abortConnection(r),r}}static async send(e,t){let r=await i.connect(e);try{return await r.send(t)}finally{await r.close()}}static async sendBatch(e,t,r={}){let s=await i.connect(e);try{return await s.sendMany(t,r)}finally{await s.close()}}static createPool(e){return new P(e,i.connect)}};0&&(module.exports={DenoMailer,Email,LogLevel,SMTPError,SmtpConnectionPool,SmtpMailer,createDenoSocketConnector,encodeHeader,signDkimMessage,validateDkimConfig});
|
package/dist/deno.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as S,b as f,c as g,d as w,e as l,f as D,g as i,h as s}from"./chunk-U6ERJXK6.mjs";function d(){let t=globalThis.Deno;if(!t)throw new Error("Deno runtime APIs are not available");return t}function r(t,e,n,o){return{readable:t.readable,writable:t.writable,closed:t.closed,close(){t.close()},startTls:o?async()=>r(await e.startTls(t,{hostname:n}),e,n,!1):void 0}}function u(t=d()){return{async connect(e){if(e.tls==="on")return r(await t.connectTls({hostname:e.hostname,port:e.port}),t,e.hostname,!1);let n=await t.connect({hostname:e.hostname,port:e.port,transport:"tcp",signal:e.signal});return r(n,t,e.hostname,e.tls==="starttls")}}}var c=class t extends i{constructor(e){super(e,u(),"DenoMailer")}static async connect(e){let n=new t(e);try{return await n.initializeSmtpSession(),n}catch(o){throw await n.abortConnection(o),o}}static async send(e,n){let o=await t.connect(e);try{return await o.send(n)}finally{await o.close()}}static async sendBatch(e,n,o={}){let a=await t.connect(e);try{return await a.sendMany(n,o)}finally{await a.close()}}static createPool(e){return new s(e,t.connect)}};export{c as DenoMailer,f as Email,l as LogLevel,D as SMTPError,s as SmtpConnectionPool,i as SmtpMailer,u as createDenoSocketConnector,S as encodeHeader,w as signDkimMessage,g as validateDkimConfig};
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { o as SmtpMailer, E as EdgeMailerOptions, i as EmailOptions, r as SmtpSendReceipt, B as BatchSendOptions, c as BatchSendResult, n as SmtpConnectionPool, e as EdgeSocketConnector } from './pool-CcxclJQJ.mjs';
|
|
2
|
+
export { A as AttachmentDisposition, a as AttachmentEncoding, b as AuthType, C as Credentials, D as DkimConfig, d as DsnOptions, f as Email, g as EmailAttachment, h as EmailAttachmentContent, L as LogLevel, M as MailBodyType, j as MailEnvelopeOptions, P as PipeliningMode, S as SMTPError, k as SMTPErrorOptions, l as SMTPStage, m as SmtpBodyType, p as SmtpPoolOptions, q as SmtpRejectedRecipient, U as User, s as encodeHeader, t as signDkimMessage, v as validateDkimConfig } from './pool-CcxclJQJ.mjs';
|
|
3
|
+
|
|
4
|
+
declare const cloudflareSocketConnector: EdgeSocketConnector;
|
|
5
|
+
declare class EdgeMailer extends SmtpMailer {
|
|
6
|
+
private constructor();
|
|
7
|
+
static connect(options: EdgeMailerOptions): Promise<EdgeMailer>;
|
|
8
|
+
static send(options: EdgeMailerOptions, email: EmailOptions): Promise<SmtpSendReceipt>;
|
|
9
|
+
static sendBatch(options: EdgeMailerOptions, emails: EmailOptions[], batchOptions?: BatchSendOptions): Promise<BatchSendResult>;
|
|
10
|
+
static createPool(options: EdgeMailerOptions): SmtpConnectionPool<EdgeMailer>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { BatchSendOptions, BatchSendResult, EdgeMailer, EdgeMailerOptions, EmailOptions, SmtpConnectionPool, SmtpMailer, SmtpSendReceipt, cloudflareSocketConnector };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { o as SmtpMailer, E as EdgeMailerOptions, i as EmailOptions, r as SmtpSendReceipt, B as BatchSendOptions, c as BatchSendResult, n as SmtpConnectionPool, e as EdgeSocketConnector } from './pool-CcxclJQJ.js';
|
|
2
|
+
export { A as AttachmentDisposition, a as AttachmentEncoding, b as AuthType, C as Credentials, D as DkimConfig, d as DsnOptions, f as Email, g as EmailAttachment, h as EmailAttachmentContent, L as LogLevel, M as MailBodyType, j as MailEnvelopeOptions, P as PipeliningMode, S as SMTPError, k as SMTPErrorOptions, l as SMTPStage, m as SmtpBodyType, p as SmtpPoolOptions, q as SmtpRejectedRecipient, U as User, s as encodeHeader, t as signDkimMessage, v as validateDkimConfig } from './pool-CcxclJQJ.js';
|
|
3
|
+
|
|
4
|
+
declare const cloudflareSocketConnector: EdgeSocketConnector;
|
|
5
|
+
declare class EdgeMailer extends SmtpMailer {
|
|
6
|
+
private constructor();
|
|
7
|
+
static connect(options: EdgeMailerOptions): Promise<EdgeMailer>;
|
|
8
|
+
static send(options: EdgeMailerOptions, email: EmailOptions): Promise<SmtpSendReceipt>;
|
|
9
|
+
static sendBatch(options: EdgeMailerOptions, emails: EmailOptions[], batchOptions?: BatchSendOptions): Promise<BatchSendResult>;
|
|
10
|
+
static createPool(options: EdgeMailerOptions): SmtpConnectionPool<EdgeMailer>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { BatchSendOptions, BatchSendResult, EdgeMailer, EdgeMailerOptions, EmailOptions, SmtpConnectionPool, SmtpMailer, SmtpSendReceipt, cloudflareSocketConnector };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";var U=Object.defineProperty;var K=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var Q=Object.prototype.hasOwnProperty;var _=(i,e)=>{for(var t in e)U(i,t,{get:e[t],enumerable:!0})},G=(i,e,t,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of V(e))!Q.call(i,s)&&s!==t&&U(i,s,{get:()=>e[s],enumerable:!(r=K(e,s))||r.enumerable});return i};var Z=i=>G(U({},"__esModule",{value:!0}),i);var ve={};_(ve,{EdgeMailer:()=>L,Email:()=>S,LogLevel:()=>$,SMTPError:()=>c,SmtpConnectionPool:()=>x,SmtpMailer:()=>C,cloudflareSocketConnector:()=>z,encodeHeader:()=>v,signDkimMessage:()=>B,validateDkimConfig:()=>O});module.exports=Z(ve);var X=new TextEncoder;function g(i){return X.encode(i)}var J=new TextDecoder("utf-8");function P(i){return J.decode(i)}function R(i,e=76){let t=g(i),r="",s=0,n=0;for(;n<t.length;){let a=t[n],o;if(a===10){r+=`\r
|
|
2
|
+
`,s=0,n++;continue}else if(a===13)if(n+1<t.length&&t[n+1]===10){r+=`\r
|
|
3
|
+
`,s=0,n+=2;continue}else o="=0D";if(o===void 0){let d=a===32||a===9,p=n+1>=t.length||t[n+1]===10||t[n+1]===13;a<32&&!d||a>126||a===61||d&&p?o=`=${a.toString(16).toUpperCase().padStart(2,"0")}`:o=String.fromCharCode(a)}s+o.length>e-3&&(r+=`=\r
|
|
4
|
+
`,s=0),r+=o,s+=o.length,n++}return r}function v(i){if(!/[^\x00-\x7F]/.test(i))return i;let e=g(i),t="";for(let r of e)r>=33&&r<=126&&r!==63&&r!==61&&r!==95?t+=String.fromCharCode(r):r===32?t+="_":t+=`=${r.toString(16).toUpperCase().padStart(2,"0")}`;return`=?UTF-8?Q?${t}?=`}var S=class i{from;to;reply;cc;bcc;subject;text;html;envelope;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 Error("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=i.toUsers(e.to),this.cc=i.toUsers(e.cc),this.bcc=i.toUsers(e.bcc),this.subject=e.subject,this.text=e.text,this.html=e.html,this.attachments=e.attachments,this.envelope=e.envelope?{...e.envelope,to:i.toEnvelopeRecipients(e.envelope.to)}:void 0,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]}static toEnvelopeRecipients(e){if(e)return Array.isArray(e)?e:[e]}getMessageData(){return this.buildMessageData(this.resolveAttachmentsSync())}async getMessageDataAsync(){return this.buildMessageData(await this.resolveAttachments())}buildMessageData(e){this.resolveHeader();let t=["MIME-Version: 1.0"];for(let[l,f]of Object.entries(this.headers))l.toLowerCase()!=="bcc"&&t.push(`${l}: ${f}`);let r=this.generateSafeBoundary("mixed_"),s=this.generateSafeBoundary("alternative_");t.push(`Content-Type: multipart/mixed; boundary="${r}"`);let a=`${t.join(`\r
|
|
5
|
+
`)}\r
|
|
6
|
+
\r
|
|
7
|
+
`,o=(e||[]).filter(l=>this.attachmentDisposition(l)==="inline"),d=(e||[]).filter(l=>this.attachmentDisposition(l)!=="inline"),p=this.generateSafeBoundary("related_");if(o.length?(a+=`--${r}\r
|
|
8
|
+
`,a+=`Content-Type: multipart/related; boundary="${p}"\r
|
|
9
|
+
\r
|
|
10
|
+
`,a+=`--${p}\r
|
|
11
|
+
`,a+=`Content-Type: multipart/alternative; boundary="${s}"\r
|
|
12
|
+
\r
|
|
13
|
+
`):(a+=`--${r}\r
|
|
14
|
+
`,a+=`Content-Type: multipart/alternative; boundary="${s}"\r
|
|
15
|
+
\r
|
|
16
|
+
`),this.text){a+=`--${s}\r
|
|
17
|
+
`,a+=`Content-Type: text/plain; charset="UTF-8"\r
|
|
18
|
+
`,a+=`Content-Transfer-Encoding: quoted-printable\r
|
|
19
|
+
\r
|
|
20
|
+
`;let l=R(this.text);a+=`${l}\r
|
|
21
|
+
\r
|
|
22
|
+
`}if(this.html){a+=`--${s}\r
|
|
23
|
+
`,a+=`Content-Type: text/html; charset="UTF-8"\r
|
|
24
|
+
`,a+=`Content-Transfer-Encoding: quoted-printable\r
|
|
25
|
+
\r
|
|
26
|
+
`;let l=R(this.html);a+=`${l}\r
|
|
27
|
+
\r
|
|
28
|
+
`}if(a+=`--${s}--\r
|
|
29
|
+
`,o.length){for(let l of o)a+=this.attachmentPart(p,l);a+=`--${p}--\r
|
|
30
|
+
`}for(let l of d)a+=this.attachmentPart(r,l);return a+=`--${r}--\r
|
|
31
|
+
`,a.endsWith(`\r
|
|
32
|
+
`)?a:`${a}\r
|
|
33
|
+
`}async resolveAttachments(){if(!this.attachments?.length)return;let e=[];for(let t of this.attachments){if(typeof t.content=="string"){e.push({...t,content:t.content});continue}if(this.isBlob(t.content)){let r=t.content;e.push({...t,content:new Uint8Array(await r.arrayBuffer()),resolvedContentType:r.type||void 0});continue}e.push({...t,content:this.attachmentBytes(t.content)})}return e}resolveAttachmentsSync(){if(this.attachments?.length)return this.attachments.map(e=>{if(typeof e.content=="string")return{...e,content:e.content};if(this.isBlob(e.content))throw new Error("Blob attachment content requires async message generation; use getMessageDataAsync(), getEmailDataAsync(), or send through a mailer");return{...e,content:this.attachmentBytes(e.content)}})}attachmentBytes(e){return e instanceof Uint8Array?e:e instanceof ArrayBuffer?new Uint8Array(e):new Uint8Array(e.buffer,e.byteOffset,e.byteLength)}isBlob(e){return typeof Blob<"u"&&e instanceof Blob}attachmentPart(e,t){let r=t.mimeType||t.contentType||t.resolvedContentType||this.getMimeType(t.filename),s=this.attachmentDisposition(t),n=t.encoding||"base64",a=`--${e}\r
|
|
34
|
+
`;return a+=`Content-Type: ${r}; name="${t.filename}"\r
|
|
35
|
+
`,a+=`Content-Description: ${t.filename}\r
|
|
36
|
+
`,t.contentId&&(a+=`Content-ID: <${t.contentId.replace(/[<>]/g,"")}>\r
|
|
37
|
+
`),a+=`Content-Disposition: ${s}; filename="${t.filename}";\r
|
|
38
|
+
`,a+=` creation-date="${new Date().toUTCString()}";\r
|
|
39
|
+
`,a+=`Content-Transfer-Encoding: ${n}\r
|
|
40
|
+
\r
|
|
41
|
+
`,a+=`${this.encodedAttachmentContent(t,n)}\r
|
|
42
|
+
\r
|
|
43
|
+
`,a}attachmentDisposition(e){return e.disposition||(e.contentId?"inline":"attachment")}encodedAttachmentContent(e,t){if(t==="base64")return typeof e.content=="string"?this.wrapBase64(e.content):this.bytesToWrappedBase64(e.content);let r=this.attachmentTextContent(e);if(t==="quoted-printable")return R(r);if(/[^\x00-\x7F]/.test(r))throw new Error("7bit attachment content must contain ASCII only");return r.replace(/\r?\n/g,`\r
|
|
44
|
+
`)}attachmentTextContent(e){return typeof e.content=="string"?e.content:P(e.content)}bytesToWrappedBase64(e){let t="";for(let r=0;r<e.length;r+=57)t&&(t+=`\r
|
|
45
|
+
`),t+=this.bytesToBase64(e.subarray(r,r+57));return t}bytesToBase64(e){let t="";for(let r=0;r<e.length;r+=32768)t+=String.fromCharCode(...e.subarray(r,r+32768));return btoa(t)}wrapBase64(e){let t="",r="";for(let s of e)/\s/.test(s)||(r+=s,r.length===76&&(t+=t?`\r
|
|
46
|
+
${r}`:r,r=""));return r&&(t+=t?`\r
|
|
47
|
+
${r}`:r),t}getEmailData(){return i.toSmtpData(this.getMessageData())}async getEmailDataAsync(){return i.toSmtpData(await this.getMessageDataAsync())}static toSmtpData(e){let t=i.applyDotStuffing(e);return t.endsWith(`\r
|
|
48
|
+
`)?`${t}.\r
|
|
49
|
+
`:`${t}\r
|
|
50
|
+
.\r
|
|
51
|
+
`}static applyDotStuffing(e){let t=e.replace(/\r\n\./g,`\r
|
|
52
|
+
..`);return t.startsWith(".")&&(t=`.${t}`),t}generateSafeBoundary(e){let t=new Uint8Array(28);crypto.getRandomValues(t);let r=Array.from(t).map(n=>n.toString(16).padStart(2,"0")).join(""),s=e+r;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.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=`"${v(this.from.name)}" <${e}>`),this.headers.From=e}resolveTo(){if(this.headers.To)return;let e=this.to.map(t=>t.name?`"${v(t.name)}" <${t.email}>`:t.email);this.headers.To=e.join(", ")}resolveSubject(){this.headers.Subject||this.subject&&(this.headers.Subject=v(this.subject))}resolveReply(){if(!this.headers["Reply-To"]&&this.reply){let e=this.reply.email;this.reply.name&&(e=`"${v(this.reply.name)}" <${e}>`),this.headers["Reply-To"]=e}}resolveCC(){if(!this.headers.CC&&this.cc){let e=this.cc.map(t=>t.name?`"${v(t.name)}" <${t.email}>`:t.email);this.headers.CC=e.join(", ")}}};var ee=["from","to","subject","date","message-id","mime-version","content-type"];function I(...i){let e=i.reduce((s,n)=>s+n.length,0),t=new Uint8Array(e),r=0;for(let s of i)t.set(s,r),r+=s.length;return t}function te(i){let e=atob(i),t=new Uint8Array(e.length);for(let r=0;r<e.length;r++)t[r]=e.charCodeAt(r);return t}function F(i){let e=i instanceof Uint8Array?i:new Uint8Array(i),t="";for(let r=0;r<e.length;r+=32768)t+=String.fromCharCode(...e.subarray(r,r+32768));return btoa(t)}function N(i){return new Uint8Array(g(i))}function re(i){if(i<128)return new Uint8Array([i]);let e=[],t=i;for(;t>0;)e.unshift(t&255),t>>=8;return new Uint8Array([128|e.length,...e])}function D(i,e){return I(new Uint8Array([i]),re(e.length),e)}function ie(i){let e=new Uint8Array([2,1,0]),t=new Uint8Array([6,9,42,134,72,134,247,13,1,1,1]),r=new Uint8Array([5,0]),s=D(48,I(t,r)),n=D(4,i);return D(48,I(e,s,n))}function se(i){let e=i.match(/-----BEGIN (PRIVATE KEY|RSA PRIVATE KEY)-----([\s\S]+?)-----END \1-----/);if(!e)throw new Error("DKIM privateKey must be a PEM private key");let t=e[1],r=te(e[2].replace(/\s+/g,""));if(t==="PRIVATE KEY")return r;if(t==="RSA PRIVATE KEY")return ie(r);throw new Error(`Unsupported DKIM private key type: ${t}`)}function j(i){return i.replace(/\r?\n/g,`\r
|
|
53
|
+
`)}function ne(i){let e=[];for(let t of j(i).split(`\r
|
|
54
|
+
`)){if(/^[ \t]/.test(t)&&e.length){let s=e[e.length-1];s.value+=`\r
|
|
55
|
+
${t}`;continue}let r=t.indexOf(":");r<1||e.push({name:t.slice(0,r),value:t.slice(r+1)})}return e}function ae(i){let e=i.name.trim().toLowerCase(),t=i.value.replace(/\r\n[ \t]+/g," ").replace(/[ \t]+/g," ").trim();return`${e}:${t}`}function oe(i){let e=j(i).split(`\r
|
|
56
|
+
`).map(t=>t.replace(/[ \t]+$/g,"").replace(/[ \t]+/g," "));for(;e.length>0&&e[e.length-1]==="";)e.pop();return e.length?`${e.join(`\r
|
|
57
|
+
`)}\r
|
|
58
|
+
`:`\r
|
|
59
|
+
`}function ce(i,e){let t=e&&e.length?e.map(n=>n.toLowerCase()):ee,r=new Set,s=[];for(let n of t)for(let a=i.length-1;a>=0;a--)if(!r.has(a)&&i[a].name.trim().toLowerCase()===n){r.add(a),s.push(i[a]);break}return s}function O(i){if(i){if(!i.domainName?.trim())throw new Error("DKIM domainName is required");if(!i.keySelector?.trim())throw new Error("DKIM keySelector is required");if(!i.privateKey?.trim())throw new Error("DKIM privateKey is required");for(let e of[i.domainName,i.keySelector])if(/[\r\n;]/.test(e))throw new Error("DKIM domainName and keySelector must be header safe")}}async function B(i,e){O(e);let t=i.indexOf(`\r
|
|
60
|
+
\r
|
|
61
|
+
`);if(t<0)throw new Error("Unable to DKIM sign message without headers and body");let r=i.slice(0,t),s=i.slice(t+4),n=ne(r),a=ce(n,e.headerFieldNames);if(!a.some(m=>m.name.toLowerCase()==="from"))throw new Error("DKIM signing requires a From header");let o=oe(s),d=await crypto.subtle.digest("SHA-256",N(o)),p=a.map(m=>m.name.trim().toLowerCase()),l=`v=1; a=rsa-sha256; c=relaxed/relaxed; d=${e.domainName}; s=${e.keySelector}; h=${p.join(":")}; bh=${F(d)}; b=`,f=[...a.map(ae),`dkim-signature:${l}`].join(`\r
|
|
62
|
+
`),h=await crypto.subtle.importKey("pkcs8",se(e.privateKey),{name:"RSASSA-PKCS1-v1_5",hash:"SHA-256"},!1,["sign"]),b=await crypto.subtle.sign("RSASSA-PKCS1-v1_5",h,N(f));return`DKIM-Signature: ${l}${F(b)}\r
|
|
63
|
+
${i}`}var q=require("cloudflare:sockets");var $=(n=>(n[n.DEBUG=0]="DEBUG",n[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR",n[n.NONE=4]="NONE",n))($||{}),A=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 le=["plain","login","cram-md5"],c=class extends Error{stage;command;response;responseCode;enhancedStatusCode;transient;cause;constructor(e,t){super(e),this.name="SMTPError",this.stage=t.stage,this.command=t.command,this.response=t.response,this.cause=t.cause;let r=t.response?.match(/^(\d{3})/);this.responseCode=r?Number(r[1]):void 0,this.enhancedStatusCode=t.response?.match(/^\d{3}[ -]([245]\.\d{1,3}\.\d{1,3})\b/)?.at(1),this.transient=this.responseCode?this.responseCode>=400&&this.responseCode<500:!1}},de=[7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21],pe=Array.from({length:64},(i,e)=>Math.floor(Math.abs(Math.sin(e+1))*4294967296)>>>0);function he(i,e){return(i<<e|i>>>32-e)>>>0}function W(...i){let e=i.reduce((s,n)=>s+n.length,0),t=new Uint8Array(e),r=0;for(let s of i)t.set(s,r),r+=s.length;return t}function k(i){let e=BigInt(i.length)*8n,t=i.length+1;for(;t%64!==56;)t++;let r=new Uint8Array(t+8);r.set(i),r[i.length]=128;let s=new DataView(r.buffer);for(let h=0;h<8;h++)s.setUint8(t+h,Number(e>>BigInt(h*8)&0xffn));let n=1732584193,a=4023233417,o=2562383102,d=271733878,p=new Array(16);for(let h=0;h<r.length;h+=64){for(let u=0;u<16;u++)p[u]=s.getUint32(h+u*4,!0);let b=n,m=a,w=o,y=d;for(let u=0;u<64;u++){let E,M;u<16?(E=m&w|~m&y,M=u):u<32?(E=y&m|~y&w,M=(5*u+1)%16):u<48?(E=m^w^y,M=(3*u+5)%16):(E=w^(m|~y),M=7*u%16);let Y=y;y=w,w=m,m=m+he(b+E+pe[u]+p[M]>>>0,de[u])>>>0,b=Y}n=n+b>>>0,a=a+m>>>0,o=o+w>>>0,d=d+y>>>0}let l=new Uint8Array(16),f=new DataView(l.buffer);return f.setUint32(0,n,!0),f.setUint32(4,a,!0),f.setUint32(8,o,!0),f.setUint32(12,d,!0),l}function ue(i){return Array.from(i).map(e=>e.toString(16).padStart(2,"0")).join("")}function me(i,e){let r=i.length>64?k(i):i,s=new Uint8Array(64).fill(92),n=new Uint8Array(64).fill(54);return r.forEach((a,o)=>{s[o]^=a,n[o]^=a}),ue(k(W(s,k(W(n,e)))))}function fe(i){let e=new Uint8Array(i.length);for(let t=0;t<i.length;t++)e[t]=i.charCodeAt(t)&255;return e}function ge(i){return fe(atob(i))}function H(i){let e="";for(let t of g(i))t>=33&&t<=126&&t!==43&&t!==61?e+=String.fromCharCode(t):e+=`+${t.toString(16).toUpperCase().padStart(2,"0")}`;return e}function ye(i){return i.includes(";")?i:`rfc822;${i}`}var C=class{constructor(e,t,r="SmtpMailer"){this.connector=t;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=le,this.startTls=e.startTls===void 0?!0:e.startTls,this.credentials=e.credentials,this.pipelining=e.pipelining===!1?!1:"auto",this.dsn=e.dsn||{},this.dkim=e.dkim,O(this.dkim),this.socketTimeoutMs=e.socketTimeoutMs||6e4,this.responseTimeoutMs=e.responseTimeoutMs||3e4,this.logger=new A(e.logLevel,`[${r}:${this.host}:${this.port}]`)}connector;host;port;secure;startTls;authType;credentials;pipelining;socketTimeoutMs;responseTimeoutMs;socket;reader;writer;responseBuffer="";logger;dsn;dkim;active=!1;closeError;sendChain=Promise.resolve();emailSending=null;queuedSendRejects=new Set;supportsDSN=!1;supportsSize=!1;maxMessageSize;supports8BitMime=!1;supportsSmtpUtf8=!1;supportsRequireTls=!1;allowAuth=!1;authTypeSupported=[];supportsStartTls=!1;supportsPipelining=!1;send(e){let t=new S(e);t.sent.catch(()=>{});let r,s=!1,n=new Promise((a,o)=>{r=p=>{s=!0,o(p)},this.queuedSendRejects.add(r),this.sendChain.then(async()=>{if(this.queuedSendRejects.delete(r),s)throw this.closedSendError();if(!this.active)throw this.closedSendError();return await this.sendEmail(t)}).then(a,o)});return this.sendChain=n.catch(()=>{}),n}async sendMany(e,t={}){let r=[];for(let s of e)try{let n=await this.send(s);r.push({status:"fulfilled",value:n})}catch(n){if(!t.continueOnError)throw n;r.push({status:"rejected",reason:n})}return r}async close(e){let t=e||new c("EdgeMailer is shutting down",{stage:"quit"});this.active=!1,this.closeError=t,this.logger.info("EdgeMailer is closed",e?.message||"");for(let r of this.queuedSendRejects)r(this.closedSendError());if(this.queuedSendRejects.clear(),!e)try{await this.writeLine("QUIT"),await this.readTimeout("quit","QUIT")}catch{}await this.closeSocket()}isActive(){return this.active}async initializeSmtpSession(){await this.openSocket(),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}async sendEmail(e){this.emailSending=e;let t=await this.prepareEmail(e),r={accepted:[],rejected:[]};try{this.canPipeline()?r=await this.envelopePipelined(t):(await this.mail(t),r=await this.rcpt(t),await this.data());let s=await this.body(t);return e.setSent(),this.createReceipt(t,r,s)}catch(s){let n=s instanceof Error?s:new c("Failed to send email",{stage:"send",cause:s});if(this.logger.error("Failed to send email: "+n.message),e.setSentError(n),this.active)try{await this.rset()}catch(a){await this.close(a instanceof Error?a:new c("Failed to reset after send error",{stage:"rset",cause:a}))}throw n}finally{this.emailSending=null}}canPipeline(){return this.pipelining==="auto"&&this.supportsPipelining}async prepareEmail(e){let t=await e.getMessageDataAsync(),r=this.dkim?await B(t,this.dkim):t,s=S.toSmtpData(r);return{email:e,data:s,size:Math.max(0,g(s).length-g(`.\r
|
|
64
|
+
`).length)}}async readTimeout(e,t){let r,s=!1,n=new c("Timeout while waiting for smtp server response",{stage:e,command:t}),a=this.read(e,t);a.catch(()=>{});try{return await Promise.race([a,new Promise((o,d)=>{r=setTimeout(()=>{s=!0,this.abortConnection(n),d(n)},this.responseTimeoutMs)})])}finally{r&&clearTimeout(r),s&&(this.closeError=n)}}async read(e,t){let r=this.shiftResponse();if(r)return r;for(;;){let{value:s,done:n}=await this.reader.read();if(n)throw new c("SMTP server closed the connection",{stage:e,command:t,response:this.responseBuffer});if(!s?.length)continue;let a=P(s).toString();this.logger.debug(`SMTP server response:
|
|
65
|
+
`+a),this.responseBuffer=this.responseBuffer+a;let o=this.shiftResponse();if(o)return o}}shiftResponse(){let e=0,t=/\r?\n/g,r;for(;r=t.exec(this.responseBuffer);){let s=this.responseBuffer.slice(e,r.index),n=r.index+r[0].length;if(/^\d{3}(?:\s|$)/.test(s)){let a=this.responseBuffer.slice(0,n);return this.responseBuffer=this.responseBuffer.slice(n),a}e=n}}async writeLine(e){await this.write(`${e}\r
|
|
66
|
+
`)}async write(e){this.logger.debug(`Write to socket:
|
|
67
|
+
`+e),await this.writer.write(g(e))}async openSocket(){this.logger.info("Connecting to SMTP server");let e=new c("Socket timeout!",{stage:"connect"}),t=new AbortController,r,s=!1;try{let n=await Promise.race([Promise.resolve(this.connector.connect({hostname:this.host,port:this.port,tls:this.secure?"on":this.startTls?"starttls":"off",signal:t.signal})),new Promise((a,o)=>{r=setTimeout(()=>{s=!0,t.abort(e),o(e)},this.socketTimeoutMs)})]);this.socket=n,this.reader=n.readable.getReader(),this.writer=n.writable.getWriter()}finally{r&&clearTimeout(r),s&&(this.closeError=e)}}async waitForSocketConnected(){let e=this.getSocket();if(!e.opened){this.logger.info("SMTP server connected");return}let t=new c("Socket timeout!",{stage:"connect"}),r;try{await Promise.race([e.opened,new Promise((s,n)=>{r=setTimeout(()=>{this.abortConnection(t),n(t)},this.socketTimeoutMs)})])}finally{r&&clearTimeout(r)}this.logger.info("SMTP server connected")}async greet(){let e=await this.readTimeout("greet");if(!e.startsWith("220"))throw new c("Failed to connect to SMTP server: "+e,{stage:"greet",response:e})}async ehlo(){let e="EHLO 127.0.0.1";await this.writeLine(e);let t=await this.readTimeout("ehlo",e);if(t.startsWith("421"))throw new c(`Failed to EHLO. ${t}`,{stage:"ehlo",command:e,response:t});if(!t.startsWith("2")){await this.helo();return}this.parseCapabilities(t)}async helo(){let e="HELO 127.0.0.1";await this.writeLine(e);let t=await this.readTimeout("helo",e);if(!t.startsWith("2"))throw new c(`Failed to HELO. ${t}`,{stage:"helo",command:e,response:t})}async tls(){let e="STARTTLS";await this.writeLine(e);let t=await this.readTimeout("starttls",e);if(!t.startsWith("220"))throw new c("Failed to start TLS: "+t,{stage:"starttls",command:e,response:t});this.reader.releaseLock(),this.writer.releaseLock();let r=this.getSocket();if(!r.startTls)throw new c("Runtime socket does not support STARTTLS",{stage:"starttls",command:e,response:t});this.socket=await r.startTls(),this.reader=this.socket.readable.getReader(),this.writer=this.socket.writable.getWriter(),this.resetCapabilities()}resetCapabilities(){this.supportsDSN=!1,this.supportsSize=!1,this.maxMessageSize=void 0,this.supports8BitMime=!1,this.supportsSmtpUtf8=!1,this.supportsRequireTls=!1,this.allowAuth=!1,this.authTypeSupported=[],this.supportsStartTls=!1,this.supportsPipelining=!1}parseCapabilities(e){this.resetCapabilities();for(let t of e.split(/\r?\n/)){let r=t.match(/^250[ -]([A-Z0-9][A-Z0-9-]*)(?:[ =](.*))?$/i);if(!r)continue;let s=r[1].toUpperCase(),n=r[2]?.trim()||"";if(s==="AUTH"){this.allowAuth=!0;for(let a of n.split(/\s+/)){let o=a.toLowerCase();(o==="plain"||o==="login"||o==="cram-md5")&&this.authTypeSupported.push(o)}}else s==="STARTTLS"?this.supportsStartTls=!0:s==="DSN"?this.supportsDSN=!0:s==="PIPELINING"?this.supportsPipelining=!0:s==="SIZE"?(this.supportsSize=!0,this.maxMessageSize=n?Number(n):void 0):s==="8BITMIME"?this.supports8BitMime=!0:s==="SMTPUTF8"?this.supportsSmtpUtf8=!0:s==="REQUIRETLS"&&(this.supportsRequireTls=!0)}}async auth(){if(this.allowAuth&&this.credentials)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 c("No supported auth method found.",{stage:"auth"})}async authWithPlain(){let e=`AUTH PLAIN ${btoa(`\0${this.credentials.username}\0${this.credentials.password}`)}`;await this.writeLine(e);let t=await this.readTimeout("auth","AUTH PLAIN");if(!t.startsWith("2"))throw new c(`Failed to plain authentication: ${t}`,{stage:"auth",command:"AUTH PLAIN",response:t})}async authWithLogin(){await this.writeLine("AUTH LOGIN");let e=await this.readTimeout("auth","AUTH LOGIN");if(!e.startsWith("3"))throw new c("Invalid login: "+e,{stage:"auth",command:"AUTH LOGIN",response:e});let t=btoa(this.credentials.username);await this.writeLine(t);let r=await this.readTimeout("auth","AUTH LOGIN username");if(!r.startsWith("3"))throw new c("Failed to login authentication: "+r,{stage:"auth",command:"AUTH LOGIN username",response:r});let s=btoa(this.credentials.password);await this.writeLine(s);let n=await this.readTimeout("auth","AUTH LOGIN password");if(!n.startsWith("2"))throw new c("Failed to login authentication: "+n,{stage:"auth",command:"AUTH LOGIN password",response:n})}async authWithCramMD5(){let e="AUTH CRAM-MD5";await this.writeLine(e);let t=await this.readTimeout("auth",e),r=t.match(/^334\s+([^\r\n]+)/)?.pop();if(!r)throw new c("Invalid CRAM-MD5 challenge: "+t,{stage:"auth",command:e,response:t});let s;try{s=ge(r)}catch(o){throw new c("Invalid CRAM-MD5 challenge encoding",{stage:"auth",command:e,response:t,cause:o})}let n=me(g(this.credentials.password),s);await this.writeLine(btoa(`${this.credentials.username} ${n}`));let a=await this.readTimeout("auth",e);if(!a.startsWith("2"))throw new c("Failed to cram-md5 authentication: "+a,{stage:"auth",command:e,response:a})}async mail(e){let t=this.mailCommand(e);await this.writeLine(t);let r=await this.readTimeout("mail",t);if(!r.startsWith("2"))throw new c(`Invalid ${t} ${r}`,{stage:"mail",command:t,response:r})}async rcpt(e){let t={accepted:[],rejected:[]},r=this.recipients(e.email);for(let s of r){let n=this.rcptCommand(s,e.email);await this.writeLine(n);let a=await this.readTimeout("rcpt",n);if(!a.startsWith("2"))throw t.rejected.push(this.rejectedRecipient(s,a)),new c(`Invalid ${n} ${a}`,{stage:"rcpt",command:n,response:a});t.accepted.push(s)}return t}async data(){let e="DATA";await this.writeLine(e);let t=await this.readTimeout("data",e);if(!t.startsWith("3"))throw new c(`Failed to send DATA: ${t}`,{stage:"data",command:e,response:t})}async envelopePipelined(e){let t={accepted:[],rejected:[]},r=this.mailCommand(e),s=this.recipients(e.email),n=s.map(l=>this.rcptCommand(l,e.email)),a="DATA";await this.writeLine(r);for(let l of n)await this.writeLine(l);await this.writeLine(a);let o,d=await this.readTimeout("mail",r);d.startsWith("2")||(o=new c(`Invalid ${r} ${d}`,{stage:"mail",command:r,response:d}));for(let[l,f]of n.entries()){let h=await this.readTimeout("rcpt",f);h.startsWith("2")?h.startsWith("2")&&t.accepted.push(s[l]):(t.rejected.push(this.rejectedRecipient(s[l],h)),o||(o=new c(`Invalid ${f} ${h}`,{stage:"rcpt",command:f,response:h})))}let p=await this.readTimeout("data",a);if(!p.startsWith("3")&&!o&&(o=new c(`Failed to send DATA: ${p}`,{stage:"data",command:a,response:p})),o)throw o;return t}async body(e){await this.write(e.data);let t=await this.readTimeout("body","<message body>");if(!t.startsWith("2"))throw new c("Failed send email body: "+t,{stage:"body",command:"<message body>",response:t});return t}createReceipt(e,t,r){return{messageId:e.email.headers["Message-ID"]||"",envelope:{from:this.mailFrom(e.email),to:this.recipients(e.email)},accepted:t.accepted,rejected:t.rejected,response:r,responseCode:this.responseCode(r),enhancedStatusCode:this.enhancedStatusCode(r),size:e.size}}rejectedRecipient(e,t){let r=this.responseCode(t);return{recipient:e,response:t,responseCode:r,enhancedStatusCode:this.enhancedStatusCode(t),transient:r?r>=400&&r<500:!1}}responseCode(e){let t=e.match(/^(\d{3})/);return t?Number(t[1]):void 0}enhancedStatusCode(e){return e.match(/^\d{3}[ -]([245]\.\d{1,3}\.\d{1,3})\b/)?.at(1)}async rset(){let e="RSET";await this.writeLine(e);let t=await this.readTimeout("rset",e);if(!t.startsWith("2"))throw new c(`Failed to reset: ${t}`,{stage:"rset",command:e,response:t})}mailCommand(e){let t=e.email,r=[`MAIL FROM: <${this.mailFrom(t)}>`],s=this.mailParameters(e);return s.length&&r.push(s.join(" ")),r.join(" ")}rcptCommand(e,t){let r=`RCPT TO: <${e}>`,s=this.rcptParameters(t);return s.length&&(r+=` ${s.join(" ")}`),r}recipients(e){return e.envelope?.to||[...e.to.map(t=>t.email),...(e.cc||[]).map(t=>t.email),...(e.bcc||[]).map(t=>t.email)]}mailFrom(e){return e.envelope?.from||e.from.email}mailParameters(e){let t=e.email,r=[],s=t.envelope;this.supportsSize&&r.push(`SIZE=${s?.size??e.size}`);let n=s?.body?.toUpperCase();if(n){if(!this.supports8BitMime)throw new c(`${n} requires 8BITMIME support`,{stage:"mail",command:"MAIL FROM"});r.push(`BODY=${n}`)}if(this.needsSmtpUtf8(t)){if(!this.supportsSmtpUtf8)throw new c("SMTPUTF8 is not supported by the SMTP server",{stage:"mail",command:"MAIL FROM"});r.push("SMTPUTF8")}if(s?.requireTls){if(!this.supportsRequireTls)throw new c("REQUIRETLS is not supported by the SMTP server",{stage:"mail",command:"MAIL FROM"});r.push("REQUIRETLS")}if(this.supportsDSN&&this.hasDsnRequest(t)){let a=this.retParameter(t);a&&r.push(a);let o=t.dsnOverride?.envelopeId||this.dsn?.envelopeId;o&&r.push(`ENVID=${H(o)}`)}return r}rcptParameters(e){if(!this.supportsDSN||!this.hasDsnRequest(e))return[];let t=[this.notificationParameter(e)],r=e.dsnOverride?.ORCPT||this.dsn?.ORCPT;return r&&t.push(`ORCPT=${H(ye(r))}`),t}notificationParameter(e){if(e.dsnOverride?.NOTIFY?.NEVER||this.dsn?.NOTIFY?.NEVER)return"NOTIFY=NEVER";let t=[];return(e.dsnOverride?.NOTIFY&&e.dsnOverride.NOTIFY.SUCCESS||!e.dsnOverride?.NOTIFY&&this.dsn?.NOTIFY?.SUCCESS)&&t.push("SUCCESS"),(e.dsnOverride?.NOTIFY&&e.dsnOverride.NOTIFY.FAILURE||!e.dsnOverride?.NOTIFY&&this.dsn?.NOTIFY?.FAILURE)&&t.push("FAILURE"),(e.dsnOverride?.NOTIFY&&e.dsnOverride.NOTIFY.DELAY||!e.dsnOverride?.NOTIFY&&this.dsn?.NOTIFY?.DELAY)&&t.push("DELAY"),t.length>0?`NOTIFY=${t.join(",")}`:"NOTIFY=NEVER"}retParameter(e){if(e.dsnOverride?.RET&&e.dsnOverride.RET.FULL||!e.dsnOverride?.RET&&this.dsn?.RET?.FULL)return"RET=FULL";if(e.dsnOverride?.RET&&e.dsnOverride.RET.HEADERS||!e.dsnOverride?.RET&&this.dsn?.RET?.HEADERS)return"RET=HDRS"}hasDsnRequest(e){return!!(e.dsnOverride?.envelopeId||e.dsnOverride?.RET||e.dsnOverride?.NOTIFY||e.dsnOverride?.ORCPT||this.dsn?.envelopeId||this.dsn?.RET||this.dsn?.NOTIFY||this.dsn?.ORCPT)}needsSmtpUtf8(e){return!!e.envelope?.smtpUtf8||[this.mailFrom(e),...this.recipients(e)].some(t=>/[^\x00-\x7F]/.test(t))}async abortConnection(e){this.active=!1,this.closeError=e instanceof Error?e:new c("SMTP connection aborted",{stage:"send",cause:e});for(let t of this.queuedSendRejects)t(this.closedSendError());this.queuedSendRejects.clear(),await this.closeSocket()}closedSendError(){return new c(this.closeError?.message||"EdgeMailer is closed",{stage:"send",cause:this.closeError})}async closeSocket(){let e=this.socket;if(e)try{await e.close()}catch{this.logger.error("Failed to close socket")}}getSocket(){if(!this.socket)throw new c("SMTP socket is not open",{stage:"connect"});return this.socket}};var x=class{constructor(e,t){this.options=e;this.connectMailer=t;let r=we(e.pool);this.maxConnections=Math.max(1,r.maxConnections??1),this.maxMessagesPerConnection=Math.max(1,r.maxMessagesPerConnection??Number.MAX_SAFE_INTEGER),this.idleTimeoutMs=Math.max(0,r.idleTimeoutMs??6e4)}options;connectMailer;maxConnections;maxMessagesPerConnection;idleTimeoutMs;ready=[];busy=new Set;waitQueue=[];pendingCreates=new Set;pendingDestroys=new Set;totalConnections=0;closed=!1;async send(e){let t=await this.acquire();try{let r=await t.mailer.send(e);return t.messages++,await this.release(t),r}catch(r){throw t.messages++,await this.release(t),r}}async sendMany(e,t={}){let r=[];for(let s of e)try{r.push({status:"fulfilled",value:await this.send(s)})}catch(n){if(!t.continueOnError)throw n;r.push({status:"rejected",reason:n})}return r}async close(){this.closed=!0;for(let t of this.waitQueue)t.reject(new Error("SMTP connection pool is closed"));this.waitQueue=[];let e=[...this.ready,...this.busy];this.ready=[],this.busy.clear();for(let t of e)this.trackDestroy(t,!1);await this.waitForDrained()}async acquire(){if(this.closed)throw new Error("SMTP connection pool is closed");let e=this.ready.shift();return e?(this.clearIdleTimer(e),this.busy.add(e),e):this.totalConnections<this.maxConnections?await this.createBusyClient():await new Promise((t,r)=>{this.waitQueue.push({resolve:t,reject:r})})}async release(e){if(e.destroyPromise){await this.trackDestroy(e);return}if(this.busy.delete(e),this.closed||!e.mailer.isActive()||e.messages>=this.maxMessagesPerConnection){await this.trackDestroy(e);return}let t=this.waitQueue.shift();if(t){this.busy.add(e),t.resolve(e);return}this.ready.push(e),this.idleTimeoutMs>0&&(e.idleTimer=setTimeout(()=>{this.ready=this.ready.filter(r=>r!==e),this.trackDestroy(e)},this.idleTimeoutMs))}dispatchWaiters(){if(!this.closed){for(;this.waitQueue.length&&this.ready.length;){let e=this.waitQueue.shift(),t=this.ready.shift();this.clearIdleTimer(t),this.busy.add(t),e.resolve(t)}for(;this.waitQueue.length&&this.totalConnections<this.maxConnections;){let e=this.waitQueue.shift();this.createBusyClient().then(e.resolve,e.reject)}}}createBusyClient(){let e=this.createBusyClientInner(),t=e.then(()=>{},()=>{});return this.pendingCreates.add(t),t.finally(()=>{this.pendingCreates.delete(t)}),e}async createBusyClientInner(){this.totalConnections++;let e=!1;try{let r={mailer:await this.connectMailer(this.options),messages:0};if(this.closed)throw e=!0,await this.trackDestroy(r,!1),new Error("SMTP connection pool is closed");return this.busy.add(r),r}catch(t){throw e||(this.totalConnections--,this.dispatchWaiters()),t}}trackDestroy(e,t=!0){let r=this.destroy(e);return this.pendingDestroys.add(r),r.finally(()=>{this.pendingDestroys.delete(r),t&&this.dispatchWaiters()}),r}destroy(e){return e.destroyPromise||(e.destroyPromise=this.destroyOnce(e)),e.destroyPromise}async destroyOnce(e){this.clearIdleTimer(e),this.ready=this.ready.filter(t=>t!==e),this.busy.delete(e);try{await e.mailer.close()}catch{}this.totalConnections=Math.max(0,this.totalConnections-1)}async waitForDrained(){for(;this.pendingCreates.size||this.pendingDestroys.size;)await Promise.all([...this.pendingCreates,...this.pendingDestroys])}clearIdleTimer(e){e.idleTimer&&(clearTimeout(e.idleTimer),e.idleTimer=void 0)}};function we(i){return i&&typeof i=="object"?i:{}}function Te(i){return i==="on"?"on":i==="starttls"?"starttls":"off"}var z={connect(i){return(0,q.connect)({hostname:i.hostname,port:i.port},{secureTransport:Te(i.tls),allowHalfOpen:!1})}},L=class i extends C{constructor(e){super(e,z,"EdgeMailer")}static async connect(e){let t=new i(e);try{return await t.initializeSmtpSession(),t}catch(r){throw await t.abortConnection(r),r}}static async send(e,t){let r=await i.connect(e);try{return await r.send(t)}finally{await r.close()}}static async sendBatch(e,t,r={}){let s=await i.connect(e);try{return await s.sendMany(t,r)}finally{await s.close()}}static createPool(e){return new x(e,i.connect)}};0&&(module.exports={EdgeMailer,Email,LogLevel,SMTPError,SmtpConnectionPool,SmtpMailer,cloudflareSocketConnector,encodeHeader,signDkimMessage,validateDkimConfig});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as L,b as g}from"./chunk-MKG2NZOU.mjs";import{a as r,b as e,c as f,d as m,e as o,f as p,g as t,h as x}from"./chunk-U6ERJXK6.mjs";export{g as EdgeMailer,e as Email,o as LogLevel,p as SMTPError,x as SmtpConnectionPool,t as SmtpMailer,L as cloudflareSocketConnector,r as encodeHeader,m as signDkimMessage,f as validateDkimConfig};
|