@visulima/email
Version:
A comprehensive email library with multi-provider support, crypto utilities, and template engines
24 lines (23 loc) • 10.7 kB
JavaScript
var B=Object.defineProperty;var D=(l,r)=>B(l,"name",{value:r,configurable:!0});import{createRequire as X}from"node:module";import m from"./EmailError-zm2ffVav.js";import V from"./RequiredOptionError-CevW3u2K.js";import Z from"./buildMimeMessage-BPtd0pno.js";import J from"./generateMessageId-11Ls5JsR.js";import ee from"./isPortAvailable-5kfsfo8u.js";import te from"./validateEmailOptions-BzlJECG5.js";import{defineProvider as re}from"./defineProvider-B9rSklAJ.js";const q=X(import.meta.url),M=typeof globalThis<"u"&&typeof globalThis.process<"u"?globalThis.process:process,O=D(l=>{if(typeof M<"u"&&M.versions&&M.versions.node){const[r,y]=M.versions.node.split(".").map(Number);if(r>22||r===22&&y>=3||r===20&&y>=16)return M.getBuiltinModule(l)}return q(l)},"__cjs_getBuiltinModule"),{Buffer:L}=O("node:buffer"),{createHmac:W,createHash:K,createSign:Y}=O("node:crypto"),{createConnection:Q}=O("node:net"),{connect:z}=O("node:tls");var se=Object.defineProperty,p=D((l,r)=>se(l,"name",{value:r,configurable:!0}),"h");const u="smtp",oe=25,ae=465,ne=1e4,ie=!1,ce=5,_=3e4,ye=re((l={})=>{if(!l.host)throw new V(u,"host");const r={authMethod:l.authMethod||"LOGIN",debug:l.debug??!1,dkim:l.dkim,host:l.host,maxConnections:l.maxConnections??ce,oauth2:l.oauth2,password:l.password,pool:l.pool??!1,port:l.port!==void 0?l.port:l.secure?ae:oe,rejectUnauthorized:l.rejectUnauthorized??!0,retries:l.retries??0,secure:l.secure??ie,timeout:l.timeout??ne,user:l.user,...l.logger&&{logger:l.logger}};let y=!1;const A=[],g=[],b=p(e=>e.replaceAll(/[\r\n\t\v\f]/g," ").trim(),"sanitizeHeaderValue"),U=p(e=>{const c=e.split(`\r
`),o={};for(const i of c)if(i.startsWith("250-")||i.startsWith("250 ")){const n=i.slice(4).trim().split(" "),s=n[0];s&&(o[s]=n.slice(1))}return o},"parseEhloResponse"),h=p(async(e,c,o)=>new Promise((i,n)=>{const s=Array.isArray(o)?o:[o];let a="",t="";const d=setTimeout(()=>{f(),n(new m(u,`Command timeout after ${r.timeout}ms: ${c?.slice(0,50)}...`))},r.timeout),f=p(()=>{e.removeListener("data",$),e.removeListener("error",v),d&&clearTimeout(d)},"cleanup"),$=p(S=>{a+=S.toString();const j=a.split(`\r
`).filter(Boolean);if(j.length>0){const I=j[j.length-1];if(I){const C=I.match(/^(\d{3})[\s-]/);if(C){const[,H]=C;t=H,I[3]===" "&&(f(),s.includes(t)?i(a):n(new m(u,`Expected ${s.join(" or ")}, got ${t}: ${a.trim()}`)))}}}},"onData"),v=p(S=>{f(),n(new m(u,`Socket error: ${S.message}`,{cause:S}))},"onError");e.on("data",$),e.on("error",v),c&&e.write(`${c}\r
`)}),"sendSmtpCommand"),k=p(async()=>{if(r.pool&&A.length>0){const e=A.pop();if(e&&!e.destroyed)return e}return r.pool&&A.length+1>=r.maxConnections?new Promise((e,c)=>{const o={reject:c,resolve:e};o.timeout=setTimeout(()=>{const i=g.indexOf(o);i!==-1&&g.splice(i,1),c(new m(u,`Connection queue timeout after ${_}ms`))},_),g.push(o)}):new Promise((e,c)=>{let o,i=!1,n;const s=p(()=>{o&&clearTimeout(o)},"cleanup");o=setTimeout(()=>{i||(i=!0,n&&!n.destroyed&&n.destroy(),s(),c(new m(u,`Connection timeout to ${r.host}:${r.port} after ${r.timeout}ms`)))},r.timeout);try{n=r.secure?z({host:r.host,port:r.port,rejectUnauthorized:r.rejectUnauthorized}):Q(r.port,r.host),n.on("error",a=>{i||(i=!0,s(),c(new m(u,`Connection error: ${a.message}`,{cause:a})))}),n.once("data",a=>{if(!i&&n){i=!0,s();const t=a.toString();t.slice(0,3)==="220"?e(n):(n.destroy(),c(new m(u,`Unexpected server greeting: ${t.trim()}`)))}})}catch(a){i||(i=!0,s(),c(new m(u,`Failed to create connection: ${a.message}`,{cause:a})))}})},"createSmtpConnection"),N=p(async e=>new Promise((c,o)=>{let i,n=!1,s;const a=p(()=>{i&&clearTimeout(i)},"cleanup");i=setTimeout(()=>{n||(n=!0,s&&!s.destroyed&&s.destroy(),a(),o(new m(u,`TLS connection timeout after ${r.timeout}ms`)))},r.timeout);try{const t={host:r.host,rejectUnauthorized:r.rejectUnauthorized,socket:e};s=z(t),s.on("error",d=>{n||(n=!0,a(),o(new m(u,`TLS connection error: ${d.message}`,{cause:d})))}),s.once("secure",()=>{!n&&s&&(n=!0,a(),c(s))})}catch(t){n||(n=!0,a(),o(new m(u,`Failed to upgrade to TLS: ${t.message}`,{cause:t})))}}),"upgradeToTLS"),x=p(e=>{if(e.destroyed||!r.pool){try{e.destroy()}catch{}return}if(g.length>0){const c=g.shift();if(c){clearTimeout(c.timeout),c.resolve(e);return}}A.push(e)},"releaseConnection"),T=p(async(e,c=!1)=>new Promise(o=>{try{if(c){e.write(`RSET\r
`),x(e),o();return}e.write(`QUIT\r
`),e.end(),e.once("close",()=>o())}catch{o()}}),"closeConnection"),P=p(async e=>{if(!r.user)return;const c=await h(e,`EHLO ${r.host}`,"250"),o=U(c),i=Object.keys(o).find(a=>a.toUpperCase()==="AUTH");if(!i&&(r.user||r.password))throw new m(u,"Server does not support authentication");const n=i?o[i]||[]:[];let{authMethod:s}=r;if(s||(n.includes("CRAM-MD5")?s="CRAM-MD5":n.includes("LOGIN")?s="LOGIN":n.includes("PLAIN")&&(s="PLAIN")),!s)throw new m(u,"No supported authentication methods");if(s==="OAUTH2"&&r.oauth2)try{const{accessToken:a,user:t}=r.oauth2,d=`user=${t}auth=Bearer ${a}`,f=L.from(d).toString("base64");await h(e,`AUTH XOAUTH2 ${f}`,"235");return}catch(a){const t=a.message;throw t.includes("535")||t.includes("Authentication failed")?new m(u,"Authentication failed: Invalid OAuth2 credentials"):a}if(s==="CRAM-MD5"&&r.password)try{const a=(await h(e,"AUTH CRAM-MD5","334")).split(" ")[1];if(!a)throw new m(u,"Invalid CRAM-MD5 challenge response");const t=L.from(a,"base64").toString("utf8"),d=W("md5",r.password);d.update(t);const f=d.digest("hex"),$=`${r.user} ${f}`;await h(e,L.from($).toString("base64"),"235");return}catch(a){const t=a.message;throw t.includes("535")||t.includes("Authentication failed")?new m(u,"Authentication failed: Invalid username or password"):a}if(s==="LOGIN"&&r.password)try{await h(e,"AUTH LOGIN","334"),await h(e,L.from(r.user).toString("base64"),"334"),await h(e,L.from(r.password).toString("base64"),"235");return}catch(a){const t=a.message;throw t.includes("535")||t.includes("Authentication failed")?new m(u,"Authentication failed: Invalid username or password"):a}if(s==="PLAIN"&&r.password)try{const a=L.from(`\0${r.user}\0${r.password}`).toString("base64");await h(e,`AUTH PLAIN ${a}`,"235");return}catch(a){const t=a.message;throw t.includes("535")||t.includes("Authentication failed")?new m(u,"Authentication failed: Invalid username or password"):a}throw new m(u,"Authentication failed - no valid credentials or method")},"authenticate"),F=p(e=>{if(!r.dkim)return e;const{domainName:c,keySelector:o,privateKey:i}=r.dkim;try{const[n,s]=e.split(`\r
\r
`);if(!n||!s)return e;const a=n.split(`\r
`),t=p(w=>w.replaceAll(`\r
`,`
`).replaceAll(/\s+/g," ").trim(),"canonicalize"),d=t(s),f=K("sha256").update(d).digest("base64"),$=["from","to","subject","date"],v=a.filter(w=>$.some(R=>w.toLowerCase().startsWith(`${R}:`))),S=v.map(w=>w.split(":")[0]?.toLowerCase()||"").filter(Boolean).join(":"),j=Math.floor(Date.now()/1e3),I={a:"rsa-sha256",bh:f,c:"relaxed/relaxed",d:c,h:S,s:o,t:j.toString(),v:"1"},C=`DKIM-Signature: ${Object.entries(I).map(([w,R])=>`${w}=${R}`).join("; ")}; b=`,H=[...v,C].map(w=>t(w)).join(`\r
`),E=Y("RSA-SHA256");E.update(H);const G=E.sign(i,"base64");return`${`${C}${G}`}\r
${a.join(`\r
`)}\r
\r
${s}`}catch(n){return console.error(`[${u}] DKIM signing error:`,n),e}},"signWithDkim");return{features:{attachments:!0,batchSending:r.pool,customHeaders:!0,html:!0,replyTo:!0,scheduling:!1,tagging:!1,templates:!1,tracking:!1},async initialize(){if(!y)try{if(!await this.isAvailable())throw new m(u,`SMTP server not available at ${r.host}:${r.port}`);y=!0}catch(e){throw new m(u,`Failed to initialize: ${e.message}`,{cause:e})}},async isAvailable(){try{if(!await ee(r.host,r.port))return!1;const e=await k();return await T(e),!0}catch{return!1}},name:u,options:r,async sendEmail(e){try{const c=te(e);if(c.length>0)return{error:new m(u,`Invalid email options: ${c.join(", ")}`),success:!1};y||await this.initialize();let o=await k();try{if(await h(o,`EHLO ${r.host}`,"250"),!r.secure)try{const t=await h(o,`EHLO ${r.host}`,"250"),d=U(t);Object.keys(d).includes("STARTTLS")&&(await h(o,"STARTTLS","220"),o=await N(o),await h(o,`EHLO ${r.host}`,"250"))}catch(t){if(r.rejectUnauthorized!==!1)throw new m(u,`STARTTLS failed or not supported: ${t.message}`,{cause:t})}await P(o),await h(o,`MAIL FROM:<${e.from.email}>`,"250");const i=[];Array.isArray(e.to)?i.push(...e.to.map(t=>t.email)):i.push(e.to.email),e.cc&&(Array.isArray(e.cc)?i.push(...e.cc.map(t=>t.email)):i.push(e.cc.email)),e.bcc&&(Array.isArray(e.bcc)?i.push(...e.bcc.map(t=>t.email)):i.push(e.bcc.email));for(const t of i)await h(o,`RCPT TO:<${t}>`,"250");await h(o,"DATA","354");let n=await Z(e);const s=[];if(e.dsn){const t=[];e.dsn.success&&t.push("SUCCESS"),e.dsn.failure&&t.push("FAILURE"),e.dsn.delay&&t.push("DELAY"),t.length>0&&s.push(`X-DSN-NOTIFY: ${t.join(",")}`)}if(e.priority){let t="";switch(e.priority){case"high":{t="1 (Highest)",s.push("Importance: High");break}case"low":{t="5 (Lowest)",s.push("Importance: Low");break}case"normal":{t="3 (Normal)",s.push("Importance: Normal");break}default:{t="3 (Normal)";break}}s.push(`X-Priority: ${t}`)}if(e.inReplyTo&&s.push(`In-Reply-To: ${b(e.inReplyTo)}`),e.references){const t=Array.isArray(e.references)?e.references.map(d=>b(d)).join(" "):b(e.references);s.push(`References: ${t}`)}if(e.listUnsubscribe){const t=Array.isArray(e.listUnsubscribe)?e.listUnsubscribe.map(d=>`<${b(d)}>`).join(", "):`<${b(e.listUnsubscribe)}>`;s.push(`List-Unsubscribe: ${t}`)}if(e.googleMailHeaders){const{googleMailHeaders:t}=e;t.feedbackId&&s.push(`Feedback-ID: ${b(t.feedbackId)}`),t.promotionalContent&&s.push("X-Google-Promotion: promotional"),t.category&&s.push(`X-Gmail-Labels: ${t.category}`)}if(s.length>0){const t=n.indexOf(`\r
\r
`);if(t!==-1){const d=n.slice(0,t),f=n.slice(t+4);n=`${d}\r
${s.join(`\r
`)}\r
\r
${f}`}}r.dkim&&(e.useDkim||e.useDkim===void 0)&&(n=F(n)),await h(o,`${n}\r
.`,"250");const a=J();return await T(o,r.pool),{data:{messageId:a,provider:u,response:"Message accepted",sent:!0,timestamp:new Date},success:!0}}catch(i){try{await T(o)}catch{}throw i}}catch(c){return{error:new m(u,`Failed to send email: ${c.message}`,{cause:c}),success:!1}}},async shutdown(){for(const e of A)try{await T(e)}catch{}A.length=0;for(const e of g)clearTimeout(e.timeout),e.reject(new Error("Provider shutdown"));g.length=0},async validateCredentials(){try{if(!await this.isAvailable())return!1;const e=await k();try{if(await h(e,`EHLO ${r.host}`,"250"),!r.secure)try{const c=await h(e,`EHLO ${r.host}`,"250"),o=U(c);if(Object.keys(o).includes("STARTTLS")){await h(e,"STARTTLS","220");const i=await N(e);Object.assign(e,i),await h(e,`EHLO ${r.host}`,"250")}}catch{if(r.rejectUnauthorized!==!1)return!1}return await P(e),await T(e),!0}catch{return await T(e),!1}}catch{return!1}}}});export{ye as default};