Node and TypeScript
The SDK, and the same call with fetch when you would rather not add a dependency.
Install
npm install @emails.sh/sdkSend
import { Emailssh } from '@emails.sh/sdk';
// The key comes from https://emails.sh/dashboard/api-keys and lives in
// EMAILSSH_API_KEY in your .env. Never in a file you commit.
const mail = new Emailssh({ apiKey: process.env.EMAILSSH_API_KEY });
const { id } = await mail.send({
from: 'Acme <hello@acme.com>',
to: ['ada@example.com'],
subject: 'Your receipt from Acme',
html: '<p>Thanks for your order.</p>',
text: 'Thanks for your order.'
});
console.log('queued', id);The constructor takes an options object with the key in it, and falls back to EMAILSSH_API_KEY when you leave apiKey out. Every method returns a promise and throws on a refusal, with the code and the message on the error.
Handle a refusal
import { Emailssh, EmailsshError } from '@emails.sh/sdk';
const mail = new Emailssh({ apiKey: process.env.EMAILSSH_API_KEY });
try {
const { id } = await mail.send({
from: 'Acme <hello@acme.com>',
to: ['ada@example.com'],
subject: 'Your receipt from Acme',
html: '<p>Thanks for your order.</p>'
});
console.log('queued', id);
} catch (err) {
if (err instanceof EmailsshError) {
// err.code is the machine-readable reason, err.nextStep says what to do
// about it in prose, and err.status is the HTTP status.
console.error(err.code, err.message, err.nextStep);
if (err.code === 'rate_limited') {
// err.retryAfter is seconds. Wait it out rather than retrying harder.
}
} else {
throw err;
}
}Without the SDK
The API is one POST. On Node 18 and newer, and on every edge runtime, fetch is built in and this is the whole integration.
export async function sendEmail(to: string, subject: string, html: string) {
const res = await fetch('https://emails.sh/v1/emails', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.EMAILSSH_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ from: 'Acme <hello@acme.com>', to: [to], subject, html })
});
if (!res.ok) {
const { error } = (await res.json()) as { error: { code: string; message: string; next?: string } };
// next says what to do about it, in prose. Keep it: it is the part a
// person, or an assistant, reads three months from now.
throw new Error(`emails.sh refused the send: ${error.code}: ${error.message} ${error.next ?? ''}`);
}
const { id } = (await res.json()) as { id: string };
return id;
}Types
The package ships its own types, so the send body is checked at compile time and an unknown field is an error rather than a silently ignored key.
import type { SendEmail, SentEmail } from '@emails.sh/sdk';
export function receipt(orderId: string, to: string): SendEmail {
return {
from: 'Acme <hello@acme.com>',
to: [to],
subject: `Receipt for order ${orderId}`,
html: `<p>Thanks for order ${orderId}.</p>`,
tags: { template: 'receipt', order_id: orderId },
idempotencyKey: `receipt-${orderId}`
};
}
export function logResult(sent: SentEmail) {
console.log(sent.id, sent.status);
}