# Send Transactional Email Without SendGrid

> Replace SendGrid for password resets and magic links: SPF, DKIM and DMARC, AWS SES vs an SMTP relay, and sending through Temps SES, Scaleway or SMTP.

David Viejo · Published 2026-03-12 · Updated 2026-10-20 · 12 min read

Canonical: https://temps.sh/blog/how-to-send-transactional-email-without-sendgrid

You don't need SendGrid to send password resets and magic links. AWS SES costs $0.10 per 1,000 emails, and a self-hosted mail server like Postal costs only the VPS it runs on. What you can't skip is the DNS work — SPF, DKIM and DMARC — and a plan for bounces.

> **TL;DR:** Most apps only need transactional email for auth flows — password resets, magic links, verification. SendGrid's paid plans start at $19.95/month (Essentials), and its free plan is now a 60-day trial. AWS SES costs [$0.10/1,000 emails](https://aws.amazon.com/ses/pricing/) but requires sandbox escape, SNS bounce handling, and IAM setup. Running your own MTA (Postfix, Postal) is free but makes IP reputation your problem. If you deploy with Temps, it sends through SES, Scaleway or any SMTP relay you configure, and adds suppression lists and open/click tracking on top.

---

## Your Options at a Glance

For a feature-by-feature Temps vs SendGrid breakdown, see [Temps vs SendGrid](https://temps.sh/compare/vs-sendgrid); this guide focuses on doing it yourself.

|                       | **SendGrid**                                                        | **AWS SES**                | **Self-hosted MTA (Postal, Postfix)**    | **Temps**                                                                                 |
| --------------------- | ------------------------------------------------------------------- | -------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------- |
| Cost                  | From $19.95/mo (Essentials, 50K emails); 60-day trial, no free plan | $0.10/1,000 emails         | Free + the server                        | Free to self-host; you pay SES, Scaleway or your relay for sending                        |
| Who delivers the mail | SendGrid                                                            | SES                        | Your server's IP                         | SES, Scaleway or your SMTP relay — Temps has no built-in mail server                      |
| DKIM / SPF            | DNS records you add                                                 | 3 DKIM CNAME records       | You configure signing                    | SES provider creates them via the SES API; for SMTP relays you configure DNS at the relay |
| Bounce handling       | Automatic                                                           | You wire SNS to a consumer | Postal: automatic; Postfix: you build it | SES bounce/complaint events (via SNS) feed a suppression list                             |
| IP reputation         | Shared or dedicated                                                 | Shared or dedicated        | Yours to build                           | Your provider's                                                                           |

on the SendGrid and AWS SES pricing pages

---

## What Does Transactional Email Actually Require?

Transactional email — password resets, magic links, OTP codes, order receipts — differs from marketing email in three ways that matter for infrastructure:

1. **Triggered by user action**, not scheduled campaigns. Latency matters. A password reset that takes 2 minutes to arrive feels broken.
2. **Legally exempt from unsubscribe requirements.** CAN-SPAM and GDPR treat transactional email as necessary for contract performance — no consent banner, no unsubscribe link required.
3. **Low volume, high importance.** A typical SaaS with 500 daily active users sends 200-500 transactional emails per day. Deliverability matters more than bulk throughput.

The six components you actually need:

- **SMTP server** — accepts outbound mail from your app
- **SPF, DKIM, DMARC** — DNS records that determine inbox placement
- **Bounce handling** — suppress future sends to invalid addresses
- **Rate limiting** — warm new IPs gradually (50-200 emails/hour at start)
- **Retry queue** — exponential backoff on temporary delivery failures
- **Template engine** — inject dynamic content into HTML emails

---

## How Do SPF, DKIM, and DMARC Work?

Treat all three as required. [Google's sender guidelines](https://support.google.com/mail/answer/81126) require every sender to Gmail to set up SPF or DKIM, and bulk senders (around 5,000+ messages a day to Gmail) to have SPF, DKIM and DMARC. Even below that threshold, missing records push mail toward spam folders.

### SPF (Sender Policy Framework)

Lists the IP addresses authorized to send email for your domain:

```txt
yourdomain.com  TXT  "v=spf1 ip4:YOUR_SERVER_IP ~all"
```

Pitfall: SPF has a 10-lookup limit. Each `include:` directive counts. Add too many third-party senders and the entire record breaks.

### DKIM (DomainKeys Identified Mail)

Adds a cryptographic signature to every outgoing message. The receiving server looks up your public key in DNS and verifies the message wasn't tampered with:

```txt
default._domainkey.yourdomain.com  TXT  "v=DKIM1; k=rsa; p=YOUR_PUBLIC_KEY"
```

### DMARC (Domain-based Message Authentication)

Ties SPF and DKIM together and tells receiving servers what to do when authentication fails:

```txt
_dmarc.yourdomain.com  TXT  "v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com"
```

Start with `p=none` for the first two weeks to monitor without blocking. Move to `p=quarantine` once you've confirmed all legitimate senders pass SPF and DKIM.

---

## Can You Build Your Own Transactional Email System?

Yes. The sending part is straightforward with Nodemailer and a simple retry queue:

```typescript
import nodemailer from 'nodemailer';

const transporter = nodemailer.createTransport({
  host: 'localhost',
  port: 587,
  secure: false,
  auth: {
    user: process.env.SMTP_USER,
    pass: process.env.SMTP_PASS,
  },
});

interface EmailJob {
  to: string;
  subject: string;
  html: string;
  retries: number;
  lastAttempt?: Date;
}

async function sendWithRetry(job: EmailJob, maxRetries = 3) {
  try {
    await transporter.sendMail({
      from: '"Your App" <noreply@yourdomain.com>',
      to: job.to,
      subject: job.subject,
      html: job.html,
    });
  } catch (error) {
    if (job.retries < maxRetries) {
      job.retries++;
      job.lastAttempt = new Date();
      const delay = Math.pow(2, job.retries) * 1000;
      setTimeout(() => sendWithRetry(job, maxRetries), delay);
    } else {
      console.error(`Failed after ${maxRetries} retries: ${job.to}`);
    }
  }
}
```

This handles the happy path. What it's missing:

- DKIM signing (another 100 lines + OpenDKIM configuration)
- Bounce classification (hard vs. soft) and suppression lists
- IP warmup management
- Blocklist monitoring (MXToolbox, Spamhaus)
- Log rotation for mail logs

Running Postfix on a VPS works well under 1,000 emails/day. Beyond that, IP reputation becomes the bottleneck. ISPs throttle new IPs heavily — warmup takes 2-4 weeks of gradually increasing volume.

---

## Is AWS SES Worth the Complexity?

SES costs $0.10 per 1,000 emails — the cheapest managed option. But the setup process is multi-step:

1. **Request production access** — SES starts in sandbox mode. Submit a use-case request; AWS reviews manually (1-3 business days).
2. **Verify your domain** — Add a CNAME to DNS. SES enables domain-level sending.
3. **Configure DKIM** — SES provides three CNAME records. Add all three.
4. **Set up bounce handling** — Create an SNS topic for bounces and complaints, subscribe a Lambda function or SQS queue to process them. Ignore bounces and AWS suspends your account.

```json
{
  "notificationType": "Bounce",
  "bounce": {
    "bounceType": "Permanent",
    "bouncedRecipients": [
      {
        "emailAddress": "invalid@example.com",
        "status": "5.1.1",
        "diagnosticCode": "smtp; 550 User unknown"
      }
    ]
  }
}
```

5. **Create IAM credentials** — Generate SMTP credentials from the SES console.
6. **Set up a configuration set** — Track delivery metrics via CloudWatch.

The cost math for a typical SaaS:

| Email type             | Daily volume | Monthly cost |
| ---------------------- | ------------ | ------------ |
| Password resets        | \~20         | $0.06        |
| Verification emails    | \~50         | $0.15        |
| Magic links            | \~100        | $0.30        |
| Activity notifications | \~200        | $0.60        |
| **Total**              | **\~370**    | **\~$1.11**  |

SES is the right call if you're already running Lambda functions and CloudWatch dashboards — the incremental cost is negligible. If you're not in the AWS ecosystem, the onboarding overhead (SNS, IAM, CloudWatch) exceeds what most small projects justify.

---

## What Are the Best Open-Source Transactional Email Alternatives?

### Postal

Postal is a full-featured, open-source mail delivery platform written in Ruby — the closest self-hosted equivalent to SendGrid.

**What you get:** SMTP server with web UI, DKIM signing, webhook notifications for delivery events, click and open tracking, IP pool management, HTTP API and SMTP interface.

**Resource cost:** Minimum 2GB RAM (4GB recommended), MySQL/MariaDB, RabbitMQ for job processing.

```bash
git clone https://github.com/postalserver/postal
cd postal
docker compose up -d
```

Best choice if you need a self-hosted SendGrid replacement with full tracking and multiple sending domains.

### Listmonk

Listmonk is primarily a newsletter manager but handles transactional email through its API. Written in Go — fast and resource-efficient (\~100MB RAM, PostgreSQL, single binary).

Good choice if you need both transactional and marketing email in one tool. For transactional-only, it's more than you need.

### Mailu

Full mail server suite — SMTP, IMAP, webmail, antispam — packaged in Docker containers. Overkill for transactional-only, but the right choice if you also want to receive email on your domain.

### Open-source options compared

| Feature           | Postal    | Listmonk | Mailu     |
| ----------------- | --------- | -------- | --------- |
| Transactional API | Yes       | Yes      | SMTP only |
| DKIM signing      | Built-in  | Manual   | Built-in  |
| Bounce handling   | Automatic | Manual   | Automatic |
| Web UI            | Yes       | Yes      | Yes       |
| Min. RAM          | 2GB       | 100MB    | 2GB       |
| Receive email     | No        | No       | Yes       |
| Cost              | Free      | Free     | Free      |
| Setup complexity  | Medium    | Low      | High      |

---

## How Does Temps Handle Transactional Email?

Temps doesn't run a mail server. It sends through an email provider you configure — AWS SES, Scaleway Transactional Email, or any SMTP relay (SES SMTP credentials, Mailgun, Postmark, your own Postfix) — and keeps the sending, tracking and suppression logic in the same binary as your deployments. The [transactional email docs](https://temps.sh/docs/transactional-email) cover setup.

### Three provider types

You add providers in the console under **Settings → Email Providers**, or through the API:

- **AWS SES** — with IAM API credentials, Temps creates the domain identity through the SES API and provisions the DKIM, SPF and MX records for you
- **Scaleway** — Scaleway Transactional Email, managed through Scaleway's API
- **SMTP** — any relay. Temps can't manage DNS through SMTP, so you set up DKIM, SPF and MX at the relay before relying on deliverability

Your applications send through the Temps API (`POST /api/emails`) with a Temps API key.

### What Temps adds on top of the provider

- A suppression list: hard bounces, complaints and manual blocks stop further sends to that address. With SES, bounce and complaint events arrive through an SNS topic you configure
- Opt-in open and click tracking per message
- A short, bounded retry on retryable provider rejections (never on ambiguous outcomes, to avoid duplicate sends)
- A record of every send in the dashboard

### What it doesn't replace

You still need a sending provider with a good reputation, and the SPF, DKIM and DMARC records described above. Compared with a SendGrid-style API, Temps replaces the dashboard, tracking and suppression handling; compared with SES alone, it replaces the SNS consumer and bookkeeping you would otherwise write yourself.

### What Temps email is designed for

- Auth flows: magic links, OTP codes, password resets, email verification
- System notifications: deploy success/failure alerts, uptime alerts
- Low-volume transactional: order confirmations, account activity notifications

It is not a marketing or newsletter tool.

Temps is free to self-host (MIT / Apache 2.0), with no per-seat fees.

---

## FAQ

### Will self-hosted email land in spam?

Not if you configure DNS correctly. SPF, DKIM, and DMARC are the three records that determine inbox placement. [Google's sender guidelines](https://support.google.com/mail/answer/81126) require SPF or DKIM for every sender and all three for bulk senders (around 5,000+ messages a day to Gmail). Start with a 2-4 week warmup — send to small batches first and increase volume gradually. Monitor sender reputation through Google Postmaster Tools (free).

### How many emails can you send from a VPS before getting blocked?

Many cloud providers block outbound port 25 (and sometimes 465) on new servers; Hetzner, for example, requires an unblock request. Submission to a relay on port 587 usually works. A single VPS with a warmed IP reliably handles 1,000-2,000 emails per day. Beyond that, you need dedicated IPs and professional-grade reputation management. Rate-limit yourself to 100-200 emails per hour when starting.

### Is AWS SES better than running your own SMTP?

For deliverability, yes — AWS has established IP reputation across massive pools. For simplicity, no — SES requires sandbox escape, SNS bounce handling, IAM credentials, and CloudWatch monitoring. If you're already deep in the AWS ecosystem, SES costs $0.10/1,000 emails and is a reasonable choice. If you're not, the setup overhead typically exceeds what small projects justify.

### Can you use a free SMTP relay?

Brevo (formerly Sendinblue) offers 300 free emails per day. These work for prototyping. The tradeoff: shared IP reputation, strict rate limits, and limited customization. For production apps, shared infrastructure creates deliverability risk. Dedicated sending — your own SMTP or a platform with native providers — gives you control over your sender reputation.

### What's the cheapest way to send transactional email?

- **Self-hosted Postfix on an existing VPS**: effectively $0 additional cost
- **AWS SES**: $0.10/1,000 emails — roughly $1/month for a typical SaaS auth flow
- **Temps (self-hosted)**: free platform; sending is billed by the provider behind it (SES, Scaleway or your relay)
- **Temps Cloud**: a coming-soon hosted control plane for your own servers, from $29/month (you still pay your hosting and email providers separately)
- **SendGrid Essentials**: from $19.95/month

---

## Stop Overpaying for Password Reset Emails

Transactional email is a solved problem sold as a monthly subscription. You don't need $19.95/month to send 200 password resets a day. You need a sending service or SMTP server, three DNS records, and a plan for bounces.

The right option depends on your stack:

- **Already on AWS**: SES at $0.10/1,000 emails is a natural fit
- **Want full control, some ops overhead**: Postal or Mailu self-hosted
- **Already deploying with Temps**: point it at SES, Scaleway or an SMTP relay and get suppression lists and tracking without another dashboard — see the [transactional email docs](https://temps.sh/docs/transactional-email)

```bash
curl -fsSL https://temps.sh/deploy.sh | bash
```

---

Source: https://temps.sh/blog/how-to-send-transactional-email-without-sendgrid · Index of all pages: https://temps.sh/llms.txt
