New:Microsoft Teams Notifications Are Now Available in Socket.Learn more →
Get Started

@vibemailai/sdk

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vibemailai/sdk

Official VibeMail SDK for TypeScript, Node and Bun

latest
Source
npmnpm
Version
1.2.0
Version published
Weekly downloads
0
-100%
Maintainers
1
Weekly downloads
 
Created
Source

VibeMail SDK for TypeScript

Official SDK for the VibeMail transactional email API. Works in Node 18+, Bun and Deno. No runtime dependencies.

npm install @vibemailai/sdk

Send an email

import { VibeMail } from "@vibemailai/sdk";

const vibemail = new VibeMail(process.env.VIBEMAIL_API_KEY!);

const { id } = await vibemail.send({
  from: "hello@yourdomain.com",
  to: "ada@example.com",
  subject: "Welcome",
  html: "<p>Glad you're here.</p>",
  text: "Glad you're here.",
});

The call returns once the API has accepted the message, not once it has been delivered. Use emails.get(id) to follow it.

Always send a text alternative alongside html. Mail with no plain-text part is measurably more likely to be spam-foldered.

Recipients

One message goes to one recipient. To reach several people, use batch() — it sends a separate message to each, which is also what stops your recipients from seeing one another's addresses:

await vibemail.emails.batch([
  { to: "ada@example.com", subject: "Welcome", text: "Hi Ada" },
  { to: "alan@example.com", subject: "Welcome", text: "Hi Alan" },
]);

Each entry succeeds or fails on its own; one bad address does not sink the rest.

const { data } = await vibemail.emails.batch(messages);
for (const result of data) {
  if ("error" in result) console.warn("rejected:", result.error);
}

Retries and idempotency

Pass an idempotencyKey on anything you might retry. A repeat carrying the same key returns the original result instead of sending a second copy — which matters because a client that times out has no way of knowing whether the message went out.

await vibemail.send({
  to: "ada@example.com",
  subject: "Receipt",
  text: "Thanks!",
  idempotencyKey: `receipt-${orderId}`,
});

The SDK retries 429 and 5xx responses on its own (twice by default, honouring Retry-After) and never retries a request the server rejected on its merits. Set maxRetries: 0 to handle that yourself.

Scheduling

const { id } = await vibemail.send({
  to: "ada@example.com",
  subject: "Reminder",
  text: "Standup in 15 minutes.",
  scheduledAt: "in 2 hours",          // or a Date, or an ISO 8601 string
});

await vibemail.emails.cancel(id);      // while it is still scheduled

Up to 30 days ahead. Once the dispatcher has claimed a message it is on its way out and can no longer be withdrawn.

Templates

const templates = await vibemail.templates.list();

await vibemail.send({
  to: "ada@example.com",
  template: "welcome",
  variables: { name: "Ada" },
});

Anything you pass explicitly — subject, html, text — overrides the stored template, so you can vary one part without redefining the rest.

React email

import { WelcomeEmail } from "./emails/welcome";

await vibemail.send({
  to: "ada@example.com",
  subject: "Welcome",
  react: <WelcomeEmail name="Ada" />,
});

Requires @react-email/render as an optional peer dependency:

npm install @react-email/render react

Your component is rendered in your process, not on our servers. It may close over anything in your application, and shipping it elsewhere to execute would mean handing us your code and your data; what leaves is plain HTML you can inspect. A plain-text alternative is generated automatically unless you pass text.

Domains

Mail sent from your own domain needs that domain added and verified. Adding one returns the DNS records to publish; it stays unverified until they resolve.

const domain = await vibemail.domains.create("yourdomain.com");

for (const [purpose, record] of Object.entries(domain.dns_records ?? {})) {
  console.log(purpose, record.type, record.host, record.value);
}
// verification TXT _vibemail-verify.yourdomain.com vm-verify-...
// mx           MX  yourdomain.com                 mail.vibemail.ai
// spf          TXT yourdomain.com                 v=spf1 include:mail.vibemail.ai -all
// dmarc        TXT _dmarc.yourdomain.com          v=DMARC1; p=quarantine; ...

dns_records is keyed by purpose, not a list: verification, mx, spf and dmarc. Each carries type, host, value, and priority where the type takes one. Publish all four; the domain verifies once they resolve.

Come back for the same records at any time, along with whether verification has gone through:

const { is_verified, dns_records } = await vibemail.domains.get(domain.id);

Listing is paged. total counts every domain on the account, not just the page you asked for:

const { data, total } = await vibemail.domains.list({ limit: 25, offset: 0 });
await vibemail.domains.delete(domain.id);

Removing a domain does not affect mail already sent from it.

Contacts

await vibemail.contacts.create({
  email: "ada@example.com",
  name: "Ada Lovelace",
  notes: "met at the analytical engine demo",
});

const { data } = await vibemail.contacts.list({ search: "ada", limit: 50 });
await vibemail.contacts.delete(data[0].id);

search matches against address and name.

Suppressions

Addresses that will not be sent to: hard bounces, spam complaints, and anything blocked by hand. A send to a suppressed address is dropped rather than attempted, which is what keeps a bad list from taking your sending reputation with it.

const { data, total } = await vibemail.suppressions.list({ limit: 100 });

for (const entry of data) {
  console.log(entry.email, entry.reason);   // "manual", or "hard bounce: ..."
}

reason is free text, not a fixed set: manual for anything added by hand or through the API, and hard bounce: ... carrying the remote server's own wording when the queue gave up on an address.

Paging

domains.list, contacts.list and suppressions.list all return the same envelope:

{ object: "list", total: 128, limit: 50, offset: 0, data: [...] }

limit defaults to 50 and is capped at 100. Walk the whole set by stepping offset until you have total:

const all = [];
for (let offset = 0; ; offset += 100) {
  const page = await vibemail.contacts.list({ limit: 100, offset });
  all.push(...page.data);
  if (all.length >= page.total) break;
}

Errors

import { VibeMailError, VibeMailTimeoutError } from "@vibemailai/sdk";

try {
  await vibemail.send({ to: "ada@example.com", subject: "Hi", text: "..." });
} catch (err) {
  if (err instanceof VibeMailError) {
    console.error(err.status, err.detail);   // 422, "'to' and 'subject' are required"
    if (err.isRetryable) { /* 429 or 5xx */ }
  } else if (err instanceof VibeMailTimeoutError) {
    // exceeded `timeout`, default 30s
  }
}
StatusMeans
400The request was malformed, such as a domain that is not a domain.
401The API key is missing, wrong, or revoked.
402A plan limit was reached. err.detail says which. Not retried.
404No such record, or not yours.
409Already exists, such as a domain someone has registered.
422Required fields missing, or a schedule more than 30 days out.
429Rate limited. Retried automatically, honouring Retry-After.
5xxOur fault. Retried automatically.

Options

Either form works:

new VibeMail("vm_live_...");
new VibeMail("vm_live_...", { timeout: 10_000 });
new VibeMail({ apiKey: "vm_live_...", timeout: 10_000 });
OptionDefault
apiKey—Required.
baseUrlhttps://vibemail.aiPoint at a self-hosted deployment.
timeout30000Per-request, in milliseconds.
maxRetries2Applies to 429 and 5xx only.
fetchglobal fetchSupply your own for tests or proxying.

API

send(input)Shorthand for emails.send.
emails.send(input)Send one message.
emails.batch(inputs, opts?)Send many, one recipient each.
emails.get(id)Status of a send.
emails.cancel(id)Withdraw a scheduled send.
templates.list()Stored templates.
domains.list(opts?)A page of sending domains.
domains.get(id)One domain, with the records that verify it.
domains.create(domain)Add a domain.
domains.delete(id)Remove a domain.
contacts.list(opts?)A page of contacts. search matches address or name.
contacts.create(contact)Store a contact.
contacts.delete(id)Forget a contact.
suppressions.list(opts?)Addresses that will not be sent to.

Send fields

Field
toRecipient. One per message; use batch() for many.
fromSender. Must be an address your account owns. Defaults to the account address.
subject
textPlain-text body. Always send one alongside html.
htmlHTML body.
reactA React element, rendered in your process. See above.
templateName or id of a stored template.
variablesValues substituted into the template.
tagsLabels carried through to analytics.
trackOpensInjects a tracking pixel into HTML sends. Defaults to on.
trackClicksRewrites links through the redirector. Defaults to on.
scheduledAtISO 8601, a Date, or a relative offset like "in 2 hours". Up to 30 days.
idempotencyKeyA retry with the same key returns the original result.

Not in the SDK yet

Webhooks and analytics are served by the API but are not wrapped here. They will be added when the shapes settle; until then reach them with fetch and the same bearer token.

Framework examples

  • Astro

License

MIT

Keywords

vibemail

FAQs

Package last updated on 28 Jul 2026

Related posts