{"_id":"@brijesh575/email-core","name":"@brijesh575/email-core","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@brijesh575/email-core","version":"1.0.0","description":"Production-grade, multi-tenant email service with pluggable providers","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","publishConfig":{"access":"public"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","lint":"tsc --noEmit","prepublishOnly":"npm run build"},"keywords":["email","smtp","ses","sendgrid","multi-tenant","nodemailer","typescript","provider"],"author":{"name":"Brijesh Kumar Kushwaha"},"license":"MIT","engines":{"node":">=18.0.0"},"dependencies":{"handlebars":"^4.7.8","nodemailer":"^6.9.16"},"devDependencies":{"@types/node":"^20.17.12","@types/nodemailer":"^6.4.17","tsup":"^8.3.5","typescript":"^5.7.3","vitest":"^2.1.8"},"peerDependencies":{"@aws-sdk/client-ses":">=3.0.0","@sendgrid/mail":">=7.0.0"},"peerDependenciesMeta":{"@aws-sdk/client-ses":{"optional":true},"@sendgrid/mail":{"optional":true}},"_id":"@brijesh575/email-core@1.0.0","_nodeVersion":"25.9.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-9baDmcjQMuF14u+w+aCxc+RFKv+7tCjajlnz4iQUfFXqu6KhRBmloKQEeEzSaDfqUAJnswIU8mMTBsoWYT15Qw==","shasum":"1097f608a271300f43f438206523511c6e8906ba","tarball":"https://registry.npmjs.org/@brijesh575/email-core/-/email-core-1.0.0.tgz","fileCount":9,"unpackedSize":210604,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDwjwoar798FoA8sQGiQ2/XQ2Vqpf6sRKL1wo4gSL6ohwIgXCuHnFBQJdy2BcqvGYHxZjYAdG0DfCXzpm39Q2WGnFI="}]},"_npmUser":{"name":"brijesh575","email":"bsingh6636@gmail.com"},"directories":{},"maintainers":[{"name":"brijesh575","email":"bsingh6636@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/email-core_1.0.0_1776008257099_0.0797838655645875"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-12T15:37:37.014Z","1.0.0":"2026-04-12T15:37:37.290Z","modified":"2026-04-12T15:37:37.494Z"},"maintainers":[{"name":"brijesh575","email":"bsingh6636@gmail.com"}],"description":"Production-grade, multi-tenant email service with pluggable providers","keywords":["email","smtp","ses","sendgrid","multi-tenant","nodemailer","typescript","provider"],"author":{"name":"Brijesh Kumar Kushwaha"},"license":"MIT","readme":"# @brijesh/email\n\nA **production-grade, multi-tenant email service** for Node.js with pluggable providers, retry/fallback, rate limiting, and Handlebars templating.\n\n## Features\n\n- **Multi-provider** — SMTP (Nodemailer), AWS SES, SendGrid, or any custom provider\n- **Multi-tenant** — per-tenant provider, credentials, templates, and rate limits\n- **Layered config** — global → environment → tenant → per-request overrides\n- **Provider fallback** — automatic failover to a backup provider\n- **Retry with backoff** — configurable exponential retry on failure\n- **In-memory rate limiting** — per-tenant abuse prevention\n- **Template engine** — Handlebars with compile caching\n- **Hooks/middleware** — `beforeSend`, `afterSend`, `onError`\n- **Preview mode** — log emails in development instead of sending\n- **Health check** — `emailClient.healthCheck()` for monitoring\n- **Pluggable logger** — default console logger with secret masking\n- **ESM + CJS** — dual output with full TypeScript types\n- **Zero lock-in** — optional peer deps for SES and SendGrid\n\n---\n\n## Installation\n\n```bash\nnpm install @brijesh/email\n```\n\n### Optional providers\n\n```bash\n# AWS SES\nnpm install @aws-sdk/client-ses\n\n# SendGrid\nnpm install @sendgrid/mail\n```\n\n---\n\n## Quick Start\n\n```ts\nimport { EmailClient } from \"@brijesh/email\";\n\nconst client = new EmailClient({\n  defaultFrom: \"noreply@myapp.com\",\n  provider: {\n    type: \"smtp\",\n    smtp: {\n      host: \"smtp.example.com\",\n      port: 587,\n      auth: { user: \"user\", pass: \"pass\" },\n    },\n  },\n});\n\nawait client.send({\n  to: \"user@example.com\",\n  subject: \"Hello!\",\n  text: \"Welcome to our app.\",\n});\n\nawait client.destroy();\n```\n\n---\n\n## Multi-Tenant Usage\n\n```ts\nconst client = new EmailClient({\n  defaultFrom: \"platform@saas.com\",\n  provider: {\n    type: \"smtp\",\n    smtp: { host: \"smtp.platform.com\", port: 587, auth: { user: \"u\", pass: \"p\" } },\n  },\n  tenants: {\n    acme: {\n      provider: {\n        type: \"ses\",\n        ses: { region: \"us-east-1\", from: \"noreply@acme.com\" },\n      },\n      from: \"noreply@acme.com\",\n      templateDir: \"./templates/acme\",\n      rateLimit: { enabled: true, maxPerMinute: 100, maxPerHour: 5000 },\n    },\n    startup: {\n      provider: {\n        type: \"sendgrid\",\n        sendGrid: { apiKey: \"SG.xxx\", from: \"hi@startup.io\" },\n      },\n      from: \"hi@startup.io\",\n    },\n  },\n});\n\n// Sends via SES with Acme's credentials\nawait client.send({\n  tenantId: \"acme\",\n  to: \"customer@gmail.com\",\n  subject: \"Welcome\",\n  template: \"welcome\",\n  data: { name: \"John\" },\n});\n\n// Register tenant at runtime\nclient.registerTenant(\"enterprise\", {\n  provider: { type: \"smtp\", smtp: { host: \"mail.ent.com\", port: 465, secure: true, auth: { user: \"a\", pass: \"b\" } } },\n  from: \"admin@enterprise.com\",\n});\n```\n\n---\n\n## Provider Setup\n\n### SMTP\n\n```ts\n{\n  type: \"smtp\",\n  smtp: {\n    host: \"smtp.example.com\",\n    port: 587,\n    secure: false,\n    auth: { user: \"user\", pass: \"pass\" },\n    pool: true,          // connection pooling\n    maxConnections: 5,\n  },\n}\n```\n\n### AWS SES\n\nRequires `@aws-sdk/client-ses` as a peer dependency.\n\n```ts\n{\n  type: \"ses\",\n  ses: {\n    region: \"us-east-1\",\n    accessKeyId: \"AKIA...\",       // optional if using IAM roles\n    secretAccessKey: \"secret...\",\n    from: \"noreply@example.com\",\n  },\n}\n```\n\n### SendGrid\n\nRequires `@sendgrid/mail` as a peer dependency.\n\n```ts\n{\n  type: \"sendgrid\",\n  sendGrid: {\n    apiKey: \"SG.xxxx\",\n    from: \"noreply@example.com\",\n  },\n}\n```\n\n### Custom Provider\n\n```ts\nimport type { EmailProvider, NormalizedEmailOptions, SendResult } from \"@brijesh/email\";\n\nclass MyProvider implements EmailProvider {\n  readonly name = \"my-provider\";\n\n  async send(options: NormalizedEmailOptions): Promise<SendResult> {\n    // your logic\n    return {\n      messageId: \"custom-id\",\n      provider: this.name,\n      accepted: options.to,\n      rejected: [],\n      timestamp: new Date(),\n    };\n  }\n}\n\n// Use it\n{\n  type: \"custom\",\n  custom: new MyProvider(),\n}\n```\n\n---\n\n## Templates (Handlebars)\n\nPlace `.hbs` files in your template directory:\n\n```\ntemplates/\n  welcome.hbs       # HTML template\n  welcome.text.hbs  # Plain text (optional)\n```\n\n```hbs\n<!-- templates/welcome.hbs -->\n<h1>Welcome, {{name}}!</h1>\n<p>Thanks for joining {{company}}.</p>\n```\n\n```ts\nawait client.send({\n  to: \"user@example.com\",\n  subject: \"Welcome\",\n  template: \"welcome\",\n  data: { name: \"Alice\", company: \"Acme\" },\n});\n```\n\n---\n\n## Attachments\n\n```ts\nawait client.send({\n  to: \"user@example.com\",\n  subject: \"Invoice\",\n  text: \"Please find attached.\",\n  attachments: [\n    { filename: \"invoice.pdf\", path: \"/path/to/invoice.pdf\" },\n    { filename: \"data.csv\", content: Buffer.from(\"a,b,c\\n1,2,3\") },\n  ],\n});\n```\n\n---\n\n## Provider Fallback\n\n```ts\nconst client = new EmailClient({\n  defaultFrom: \"noreply@app.com\",\n  provider: { type: \"ses\", ses: { region: \"us-east-1\", from: \"noreply@app.com\" } },\n  fallbackProvider: { type: \"smtp\", smtp: { host: \"smtp.backup.com\", port: 587 } },\n});\n```\n\nIf the primary provider fails after retries, the fallback provider is tried automatically.\n\n---\n\n## Retry Configuration\n\n```ts\n{\n  retry: {\n    enabled: true,\n    maxAttempts: 3,\n    initialDelayMs: 1000,\n    maxDelayMs: 30000,\n    backoffMultiplier: 2,\n  },\n}\n```\n\n---\n\n## Rate Limiting\n\n```ts\n{\n  rateLimit: {\n    enabled: true,\n    maxPerMinute: 60,\n    maxPerHour: 1000,\n  },\n}\n```\n\nThrows `RateLimitError` when limits are exceeded.\n\n---\n\n## Hooks\n\n```ts\nconst client = new EmailClient({\n  // ...\n  hooks: {\n    beforeSend: [(options) => {\n      // Modify options, add tracking headers, etc.\n      return { ...options, headers: { ...options.headers, \"X-Track\": \"abc\" } };\n    }],\n    afterSend: [(result, options) => {\n      console.log(`Sent ${result.messageId} to ${options.to}`);\n    }],\n    onError: [(error, options) => {\n      console.error(`Failed to send to ${options.to}: ${error.message}`);\n    }],\n  },\n});\n```\n\n---\n\n## Preview Mode\n\nIn development, log emails instead of sending:\n\n```ts\nconst client = new EmailClient({\n  preview: true,\n  defaultFrom: \"dev@localhost\",\n  provider: { type: \"smtp\", smtp: { host: \"localhost\", port: 1025 } },\n});\n```\n\n---\n\n## Health Check\n\n```ts\nconst health = await client.healthCheck();\n// { status: \"healthy\", providers: { ... }, timestamp: Date }\n```\n\n---\n\n## Environment Variables\n\nThe library reads `.env` variables as fallback for SMTP when no provider is configured:\n\n| Variable | Description |\n|---|---|\n| `NODE_ENV` | `development`, `staging`, `production` |\n| `SMTP_HOST` | SMTP server host |\n| `SMTP_PORT` | SMTP server port (default: 587) |\n| `SMTP_SECURE` | Use TLS (`true`/`false`) |\n| `SMTP_USER` | SMTP username |\n| `SMTP_PASS` | SMTP password |\n| `SMTP_FROM` | Default from address |\n\n---\n\n## Error Handling\n\n```ts\nimport { EmailError, ProviderError, ConfigError, ValidationError, RateLimitError } from \"@brijesh/email\";\n\ntry {\n  await client.send({ /* ... */ });\n} catch (err) {\n  if (err instanceof ValidationError) {\n    console.log(\"Invalid input:\", err.field);\n  } else if (err instanceof RateLimitError) {\n    console.log(\"Rate limited:\", err.tenantId);\n  } else if (err instanceof ProviderError) {\n    console.log(\"Provider failed:\", err.provider, err.cause);\n  }\n}\n```\n\nAll errors use the standard `cause` chain — never leak credentials.\n\n---\n\n## API Reference\n\n### `new EmailClient(config?)`\n\nCreates a new client instance.\n\n### `client.send(options)`\n\nSend an email. Returns `Promise<SendResult>`.\n\n### `client.healthCheck()`\n\nReturns `Promise<HealthCheckResult>`.\n\n### `client.registerTenant(id, config)`\n\nRegister or update a tenant at runtime.\n\n### `client.removeTenant(id)`\n\nRemove a tenant and clean up its resources.\n\n### `client.destroy()`\n\nGracefully shut down all providers and caches.\n\n---\n\n## Build\n\n```bash\nnpm run build     # ESM + CJS + types via tsup\nnpm test          # Vitest\nnpm run lint      # TypeScript check\n```\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-2853f36a380706b59a6009093f3f3f57"}