JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 69
  • Score
    100M100P100Q94646F
  • License MIT

EmailMate SDK — Resend-compatible email API (emailmate.dev)

Package Exports

  • emailmate
  • emailmate/convex

Readme

emailmate

Email API that doesn't eat your margins.

New here? Simple SDK. React Email baked in.

Coming from Resend? One line change. No refactor.

Install

npm install emailmate

Quick Start

import { EmailMate } from 'emailmate';

const emailmate = new EmailMate('em_xxxxxxxxxxxx');

await emailmate.emails.send({
  from: 'you@yourapp.com',
  to: 'user@example.com',
  subject: 'Welcome',
  html: '<h1>Welcome aboard!</h1>'
});

Coming from Resend?

One line. That's it.

// Before
import { Resend } from 'resend';

// After
import { Resend } from 'emailmate';

// Your code? Untouched. Same API. Same types. Same everything.
const resend = new Resend('em_xxxxxxxxxxxx');
await resend.emails.send({ ... });

Configuration

import { EmailMate } from 'emailmate';

// Default (emailmate.dev cloud)
const em = new EmailMate('em_xxxxxxxxxxxx');

// Self-hosted / BYOS (Bring Your Own SES)
const em = new EmailMate('em_xxxxxxxxxxxx', {
  baseUrl: 'https://your-instance.com'
});
Parameter Type Default Description
apiKey string required Your API key (em_...)
config.baseUrl string https://www.emailmate.dev API base URL

Emails

Send an email

const { id } = await em.emails.send({
  from: 'Acme <hello@acme.com>',
  to: 'user@example.com',
  subject: 'Welcome to Acme',
  html: '<h1>Welcome!</h1><p>Thanks for signing up.</p>'
});

Parameters:

Field Type Required Description
from string Yes Sender (Name <email> or email)
to string | string[] Yes Recipient(s)
subject string Yes Email subject
html string No* HTML body
text string No Plain text body (auto-generated if omitted)
react ReactElement No* React Email component
reply_to string | string[] No Reply-to address(es)
cc string | string[] No CC recipients
bcc string | string[] No BCC recipients
headers Record<string, string> No Custom headers
attachments Attachment[] No File attachments
tags Tag[] No Email tags for tracking
scheduled_at string No ISO 8601 datetime for scheduled send

*Provide either html or react, not both.

Response: { id: string, object: "email" }

With React Email

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

await em.emails.send({
  from: 'Acme <hello@acme.com>',
  to: 'user@example.com',
  subject: 'Welcome',
  react: <WelcomeEmail name="John" />
});

With attachments

await em.emails.send({
  from: 'billing@acme.com',
  to: 'user@example.com',
  subject: 'Your invoice',
  html: '<p>Invoice attached.</p>',
  attachments: [{
    filename: 'invoice.pdf',
    content: Buffer.from(pdfBytes),
    content_type: 'application/pdf'
  }]
});

With tags

await em.emails.send({
  from: 'hello@acme.com',
  to: 'user@example.com',
  subject: 'Welcome',
  html: '<p>Welcome!</p>',
  tags: [
    { name: 'category', value: 'onboarding' },
    { name: 'app', value: 'acme' }
  ]
});

Schedule an email

await em.emails.send({
  from: 'hello@acme.com',
  to: 'user@example.com',
  subject: 'Reminder',
  html: '<p>Don\'t forget!</p>',
  scheduled_at: '2025-04-01T10:00:00Z'
});

Get an email

const email = await em.emails.get('em_xxxx');
// { id, object, to, from, subject, html, text, created_at, last_event }

Response:

Field Type Description
id string Email ID
to string[] Recipients
from string Sender
subject string Subject
html string HTML body
text string Plain text body
created_at string ISO 8601 timestamp
last_event string Latest event (sent, delivered, opened, bounced, etc.)

Cancel a scheduled email

const { id, canceled } = await em.emails.cancel('em_xxxx');

Domains

Add a domain

const domain = await em.domains.create({ name: 'acme.com' });
// { id, name, status: "pending", records: [...] }

List domains

const { data } = await em.domains.list();
// data: Domain[]

Get domain details

const domain = await em.domains.get('dom_xxxx');

Domain object:

Field Type Description
id string Domain ID
name string Domain name
status "pending" | "verified" | "failed" Verification status
created_at string ISO 8601 timestamp
records DnsRecord[] DNS records to configure

DnsRecord:

Field Type Description
type string TXT, CNAME, MX
name string Record name
value string Record value
status "pending" | "verified" Record status

Verify a domain

const domain = await em.domains.verify('dom_xxxx');

Delete a domain

await em.domains.delete('dom_xxxx');

Audiences

Create an audience

const audience = await em.audiences.create({ name: 'Newsletter' });
// { id, name, created_at }

List audiences

const { data } = await em.audiences.list();

Get an audience

const audience = await em.audiences.get('aud_xxxx');

Delete an audience

await em.audiences.delete('aud_xxxx');

Contacts

Add a contact

const contact = await em.contacts.create('aud_xxxx', {
  email: 'user@example.com',
  first_name: 'Jane',
  last_name: 'Doe',
  unsubscribed: false
});
Field Type Required Description
email string Yes Contact email
first_name string No First name
last_name string No Last name
unsubscribed boolean No Opt-out status (default: false)

List contacts

const { data } = await em.contacts.list('aud_xxxx');

Get a contact

const contact = await em.contacts.get('aud_xxxx', 'con_xxxx');

Update a contact

await em.contacts.update('aud_xxxx', 'con_xxxx', {
  first_name: 'Janet',
  unsubscribed: true
});

Delete a contact

await em.contacts.delete('aud_xxxx', 'con_xxxx');

API Keys

Create an API key

const { id, token } = await em.apiKeys.create({
  name: 'Production',
  permission: 'full_access' // or 'sending_access'
});
// token is only shown once — store it securely

List API keys

const { data } = await em.apiKeys.list();

Delete an API key

await em.apiKeys.delete('key_xxxx');

HTML Helpers

Built-in utilities for building email templates without React Email.

import { wrap, btn, p, greeting, center, hint, htmlToText } from 'emailmate';

const html = wrap(
  greeting('Jane') +
  p('Thanks for signing up. Your account is ready.') +
  center(btn('https://acme.com/dashboard', 'Go to Dashboard')) +
  hint('If you didn\'t create this account, ignore this email.'),
  { brand: 'Acme', tagline: 'Ship faster', url: 'https://acme.com', domain: 'acme.com' }
);

// Auto-generate plain text from HTML
const text = htmlToText(html);

await em.emails.send({ from, to, subject, html, text });
Function Description
wrap(body, opts?) Full HTML email wrapper with brand footer
btn(href, label, color?) CTA button (default: dark)
p(text) Styled paragraph
greeting(name) "Hi {name}," paragraph
center(content) Center-aligned wrapper
hint(text) Small gray footnote text
htmlToText(html) Strip HTML → plain text

Convex Integration

For apps using Convex as their backend:

import { EmailMate } from 'emailmate';
import { internalAction } from './_generated/server';
import { v } from 'convex/values';

const em = new EmailMate(process.env.EMAILMATE_API_KEY!);

export const sendWelcome = internalAction({
  args: { email: v.string(), name: v.string() },
  handler: async (_ctx, { email, name }) => {
    await em.emails.send({
      from: 'Acme <hello@acme.com>',
      to: email,
      subject: `Welcome, ${name}!`,
      html: `<h1>Welcome!</h1><p>Hey ${name}, your account is ready.</p>`
    });
  },
});

Important: In Convex, emails must be sent from internalAction (not mutations). Mutations schedule actions:

// In a mutation
ctx.scheduler.runAfter(0, internal.emails.sendWelcome, { email, name });

Error Handling

try {
  await em.emails.send({ ... });
} catch (error) {
  // { code: 'validation_error', message: 'Missing required field: to' }
  console.error(error.code, error.message);
}
Error Code Description
validation_error Missing or invalid fields
unauthorized Invalid API key
forbidden Domain not verified
not_found Resource doesn't exist
rate_limit_exceeded Too many requests
internal_error Server error

Types

All types are exported:

import type {
  SendEmailOptions,
  SendEmailResponse,
  Email,
  Domain,
  DnsRecord,
  Audience,
  Contact,
  ApiKey,
  Attachment,
  Tag,
  EmailMateConfig,
  EmailMateError,
} from 'emailmate';

License

MIT