# Transactional email for indie hackers

3,000 emails a month free, $20 for 50,000, no sales call and no minimum. Add a verified domain and start sending in an afternoon.

A side project sends a few hundred emails a month, so the only pricing question that matters is what happens below the point where an enterprise plan makes sense. The free tier covers 3,000 emails a month and 100 a day with no credit card, and Pro is $20 a month for 50,000. There is no contract, no minimum commitment, and no call with anyone.

## What happens next

1. **Sign up and take a key.** https://emails.sh/dashboard. The key starts with esh_ and works immediately, sending from onboarding@emails.sh.
1. **Verify the domain you already own.** Two DNS records at your registrar. Once verified, mail comes from you, which is the difference between landing in the inbox and landing in spam.
1. **Install the SDK and write one send function.** `npm install @emails.sh/sdk` or `pip install emailssh`. One module reads EMAILSSH_API_KEY, everything else imports it.
1. **Tag your sends so the logs stay useful.** tags: { type: "receipt" } costs nothing now and saves an hour when a customer says a receipt never arrived.

## The four emails a small product actually sends

src/email.ts, the whole email layer of a one-person SaaS.

```ts
import { Emailssh } from '@emails.sh/sdk';

const emails = new Emailssh(process.env.EMAILSSH_API_KEY!);

const FROM = 'Acme <hello@acme.com>';

// Every send is tagged. Tags are what make the delivery log searchable when a
// customer emails you saying they never got something.
export const sendVerification = (to: string, link: string) =>
	emails.send({
		from: FROM,
		to: [to],
		subject: 'Confirm your email',
		html: `<p><a href="${link}">Confirm your email</a></p>`,
		text: `Confirm your email: ${link}`,
		tags: { type: 'verification' }
	});

export const sendPasswordReset = (to: string, link: string) =>
	emails.send({
		from: FROM,
		to: [to],
		subject: 'Reset your password',
		html: `<p><a href="${link}">Reset your password</a></p><p>Expires in an hour.</p>`,
		text: `Reset your password (expires in an hour): ${link}`,
		tags: { type: 'password-reset' }
	});

export const sendReceipt = (to: string, orderId: string, amount: string) =>
	emails.send({
		from: FROM,
		to: [to],
		replyTo: 'support@acme.com',
		subject: `Receipt for order ${orderId}`,
		html: `<p>Order ${orderId}, ${amount}. Thanks.</p>`,
		text: `Order ${orderId}, ${amount}. Thanks.`,
		tags: { type: 'receipt' },
		idempotencyKey: `receipt:${orderId}`
	});

export const sendTrialEnding = (to: string, days: number) =>
	emails.send({
		from: FROM,
		to: [to],
		subject: `Your trial ends in ${days} days`,
		html: `<p>Your trial ends in ${days} days. Add a card to keep your data.</p>`,
		text: `Your trial ends in ${days} days. Add a card to keep your data.`,
		tags: { type: 'trial-ending' }
	});
```

## Worth knowing

### Free-tier limits are per day as well as per month

3,000 a month, 100 a day. A launch that sends 400 verification emails in one evening hits the daily limit before the monthly one, which is worth knowing the day before you post.

### Transactional and marketing should not share a domain

Send campaigns from news.acme.com and receipts from acme.com. One bad campaign then cannot take your password resets down with it.

### Set idempotencyKey on anything triggered by a webhook

Payment providers retry. Without the key, one retried webhook is two receipts and a confused customer.

### You can leave without an export ticket

The API is one endpoint and your templates are your own files. Migrating away is changing a base URL and a key, which is the honest reason to try it.


## Questions

### What happens if I go over the free tier?

Sends are rejected with an error that says so rather than being silently dropped. Upgrading to Pro at $20 a month takes it to 50,000.

### Is there a sales call?

No. Sign up, take a key, send. The enterprise plan is the only one with a human in the loop.

### Can I send from more than one product?

Yes. Verify each domain and use a separate API key per product, so revoking one does not affect the other.

### Do you charge for delivery logs?

No. GET /v1/emails/:id and the dashboard history are part of every plan including free.

### What about a newsletter?

/v1/audiences covers a small contact list. If sending campaigns is the main job, a dedicated marketing tool will serve you better.


## One send

```
To: buyer@example.com
Subject: Receipt for order 3391

Order 3391, $12.00. Thanks.
```

Docs: https://emails.sh/docs.md