log in
consulting hosting industries the daily tools about contact

Resend Is What Transactional Email Should Have Always Been

I've sent email through a lot of APIs. Resend is the first one that didn't make me feel like I was wrestling with legacy enterprise software.

I've wired up transactional email through SendGrid, Mailgun, Postmark, SES, and a few others I've tried to forget. They all work, technically. But every one of them has that same energy — like the API was designed by a committee in 2012 and then wrapped in a React dashboard someone built during a hackathon. Resend doesn't feel like that. It feels like someone actually asked "what do developers need here" and then built exactly that.

The Problem Transactional Email Has Always Had

Sending email from an application is conceptually simple. You have an event — a user signs up, an order ships, a password resets — and you need to fire off a well-formatted message with the right data. That's it.

The reality is that every ESP I've used turns this into a project. You're fighting deliverability settings, IP warm-up schedules, template languages that differ from anything else in your stack, webhook payloads that need their own parser, and dashboards where basic things like "show me the actual HTML that got sent" are either buried or paywalled.

SendGrid specifically has a special talent for making simple things complicated. The v3 Mail Send endpoint has a JSON structure that looks like it was designed to handle every possible email use case simultaneously, which means even a basic transactional send requires you to construct a nested object that feels like you're filing paperwork. Their Laravel integration (sendgrid/sendgrid-php) works, but it sits outside the normal Laravel mail pipeline in ways that create friction.

Resend's pitch is simple: React Email for templates, a clean REST API, an official Laravel package that drops into the standard Mail facade, and logs that actually show you what happened. That's the whole thing. And it mostly delivers.

Getting It Running in Laravel

The setup is genuinely fast. Install the package, swap the mailer, done.

composer require resend/resend-laravel

In .env:

MAIL_MAILER=resend
RESEND_API_KEY=re_your_key_here

In config/mail.php, add the mailer if it's not auto-discovered (on newer versions of the package it usually is):

'resend' => [
    'transport' => 'resend',
],

Now your existing Mailable classes just work. Here's a real-world example — a shipment notification I use for an e-commerce client:

class ShipmentNotification extends Mailable
{
    use Queueable, SerializesModels;

    public function __construct(
        public Order $order,
        public string $trackingNumber,
        public string $carrier
    ) {}

    public function envelope(): Envelope
    {
        return new Envelope(
            subject: 'Your order has shipped — ' . $this->order->reference,
            replyTo: [
                new Address('support@example.com', 'Support'),
            ]
        );
    }

    public function content(): Content
    {
        return new Content(
            view: 'emails.shipment-notification',
        );
    }
}

Dispatch it the normal way:

Mail::to($order->customer_email)->send(new ShipmentNotification(
    order: $order,
    trackingNumber: $tracking,
    carrier: 'UPS'
));

No SDK gymnastics. No custom transport class. The Laravel mail abstraction works exactly as it's supposed to.

If you want to use Resend's API directly — say, for a script outside Laravel or when you need to access response metadata — the raw client is clean too:

$resend = Resend::client(config('services.resend.key'));

$response = $resend->emails->send([
    'from'    => 'Acme <noreply@mail.acme.com>',
    'to'      => [$order->customer_email],
    'subject' => 'Order Confirmed',
    'html'    => $html,
    'tags'    => [
        ['name' => 'order_id', 'value' => (string) $order->id],
        ['name' => 'environment', 'value' => app()->environment()],
    ],
]);

$emailId = $response->id; // store this for later lookup

That $response->id is worth saving. Resend's API lets you retrieve the full email record by ID later, including delivery status. I store these on the relevant model when I need an audit trail — healthcare and e-commerce clients both care about this.

The Quirks That Will Bite You

Domain verification is stricter than you expect. Resend requires you to own and verify the sending domain. You can't send from a Gmail or personal address in production. This is correct behavior for deliverability, but if you're migrating a client whose previous developer was sending from noreply@gmail.com (yes, this happens), you need to have that conversation before you flip the switch.

The free tier is 3,000 emails/month and then it stops. Not throttles — stops. I had a staging environment start sending more test emails than expected (a queue worker that got misconfigured), and it quietly hit the limit. The dashboard showed it, but I didn't get a warning email until after the fact. Set up a billing alert and monitor your usage if you're on the free plan with real traffic.

Webhooks use Svix under the hood. This is fine, and Svix is solid, but the webhook payload structure and signature verification is Svix's format, not something Resend invented. If you're used to SendGrid's event webhook format, expect to rewrite your handler. Here's a minimal Laravel webhook receiver:

Route::post('/webhooks/resend', function (Request $request) {
    // Verify the signature using the webhook secret from your Resend dashboard
    $webhookSecret = config('services.resend.webhook_secret');

    try {
        $wh = new \Svix\Webhook($webhookSecret);
        $payload = $wh->verify(
            $request->getContent(),
            $request->headers->all()
        );
    } catch (\Exception $e) {
        return response()->json(['error' => 'Invalid signature'], 400);
    }

    $event = $payload['type'] ?? null;

    match ($event) {
        'email.delivered'    => handleDelivered($payload['data']),
        'email.bounced'      => handleBounce($payload['data']),
        'email.complained'   => handleComplaint($payload['data']),
        default              => null,
    };

    return response()->json(['ok' => true]);
});

Add svix/svix to your composer dependencies. Resend's docs mention this but don't make it prominent.

Attachments from storage work fine, but watch memory. If you're attaching generated PDFs from S3, pull the file into a temp path rather than loading the whole thing into a string. Large in-memory attachments in queued jobs will occasionally exhaust your worker's memory limit and fail silently depending on your queue configuration.

React Email is optional, not required. The marketing leans hard into the React Email angle, and it's genuinely nice if you're in a JS/TS shop. But you're not forced to use it. My Laravel installs use Blade templates with the standard @component mail layout, and it works perfectly. Don't let the React framing make you think this is a JS-only tool.

When I'd Reach for Resend

I'd reach for Resend any time I'm building a new Laravel app that needs transactional email. The developer experience is clearly better than SendGrid's, and the pricing is honest — you know what you're paying and why.

I'd specifically prefer it over SendGrid for:

  • Small-to-medium volume apps (under a few million emails/month) where I don't need dedicated IPs and warmup management
  • Projects where a developer — not a marketing team — owns the email code
  • Clients where I need a clean audit trail and the email log UI is part of my support story
  • Any greenfield project where I get to pick the stack

I'd think harder before using it if:

  • The client already has a deeply embedded SendGrid setup with marketing automations, suppression lists built over years, and a dedicated deliverability relationship
  • Volume is high enough that I need dedicated IPs from day one
  • I need sophisticated marketing email features alongside transactional (Resend is transactional-first; it's not Klaviyo)
  • The client is in a regulated space where vendor lock-in audits matter and they need an enterprise MSA

For a small biotech I worked with last year — internal notifications, experiment status updates, that kind of thing, maybe 5,000 emails a month — Resend was the right call. Low volume, developer-maintained, clean logs when something inevitably needed debugging. SendGrid would have been overkill with its complexity and a dashboard full of features they'd never use.

Where SendGrid Still Wins

I want to be fair: SendGrid has 15 years of deliverability infrastructure, IP reputation management, ISP relationships, and suppression list tooling that Resend hasn't had time to build. If you're sending transactional email at serious scale, or you have a client whose marketing team needs to be in the same platform as the transactional sends, SendGrid's maturity matters.

Also, if you've been on a dedicated IP for two years and your deliverability is dialed in, you don't blow that up to chase a nicer API. Inbox placement is worth more than developer ergonomics.

But for new projects? The calculus has shifted. Resend's API is genuinely better designed. The Laravel integration is first-class. The logs are useful. And I haven't had to look at a table with 47 columns of settings I don't understand just to send a password reset.


The developer-focused email API isn't a new idea — Postmark has been doing it well for years, and Mailgun was developer-first before it got acquired and slowly turned into something else. Resend is the latest iteration, and right now it's the sharpest one. I'm reaching for it by default on new projects until I see a reason not to.

Need help shipping something like this? Get in touch.