Client Area

Transactional Email: SMTP, SendGrid, AWS SES, Mailgun — Compared

ByDomain India Team·DomainIndia Support
9 min readPublished 22 Apr 2026Updated 23 Jun 2026512 views

In this article

  • 1Why native `mail()` fails for transactional email at scale
  • 2The options, compared
  • 31. SMTP via cPanel mail account
  • 42. SendGrid (by Twilio)
  • 53. AWS SES

Transactional Email: SMTP, SendGrid, AWS SES, Mailgun — Compared

Password resets, order receipts, account notifications — every application sends transactional email. Deliverability, not volume, is the primary concern. This guide compares the options, shows the SPF / DKIM / DMARC setup that makes everything else work, and provides PHP and Node.js integration examples for the major providers.

Why native mail() fails for transactional email at scale

PHP's built-in mail() function is the tempting starting point. It works for low volume on well-configured servers. At scale, three problems surface:

  1. Deliverability. Shared hosting IP addresses are usually on some blocklist or another. Email lands in spam, or gets rejected outright.
  2. No observability. The mail() function returns true as long as the mail was handed off to sendmail — not when it actually got delivered. You don't know which mails bounced, which were opened, which were clicked.
  3. SPF / DKIM alignment is hard. On shared hosting you share the sending IP and domain with hundreds of other sites; the email infrastructure is not tuned for your domain specifically.

For transactional volume above a few dozen per day — password resets, order confirmations, alerts — use a dedicated service.

The options, compared

1. SMTP via cPanel mail account

  • Cost: free, included with hosting
  • Volume limit: roughly 200–500 emails / hour on shared plans
  • Deliverability: acceptable for your own domain after SPF + DKIM are set
  • Observability: basic logs in cPanel
  • Good for: single-business sites, contact form notifications, low-volume transactional

2. SendGrid (by Twilio)

  • Cost: free tier 100 emails / day forever; paid from $15 / mo for 40,000 emails
  • Deliverability: excellent — dedicated IPs, well-maintained reputation
  • Observability: full dashboard — opens, clicks, bounces, unsubscribes
  • Good for: growing apps; mix of transactional and marketing

3. AWS SES

  • Cost: cheapest at scale — $0.10 per 1,000 emails
  • Deliverability: solid on shared IPs, excellent on dedicated (paid extra)
  • Observability: SNS-based event feeds (bounces, complaints, deliveries)
  • Setup friction: requires AWS account, initial sandbox mode (capped to verified senders), verification flow to leave sandbox
  • Good for: volume senders on AWS-integrated stacks

4. Mailgun

  • Cost: pay-as-you-go from $35 / mo for 50,000 emails
  • Deliverability: strong, especially for developers (API-first)
  • EU data-centre option available (GDPR alignment)
  • Good for: developer-first setups, European businesses

5. Postmark

  • Cost: $15 / mo for 10,000 emails — more expensive per email than SendGrid/SES
  • Deliverability: industry-leading, specifically optimised for transactional
  • Strict policy: transactional only, no marketing — if you mix, you get kicked off
  • Good for: finance / healthcare / critical transactional (password resets for security-sensitive apps)

6. Amazon SES via cPanel relay

  • Your cPanel account's SMTP configured to forward via SES
  • Combines cheap SES pricing with cPanel-native integration
  • Setup: more involved, but cuts SES sandbox issues

Recommendation matrix

VolumeUse caseRecommended
< 100 / dayContact forms, small storecPanel SMTP
100 – 5,000 / dayNormal SaaSSendGrid free / paid
5,000 – 100,000 / dayGrowing SaaSSendGrid paid / Mailgun
> 100,000 / dayHigh volumeAWS SES
Critical (password reset)AnyPostmark

SPF, DKIM, DMARC — required for all

Before choosing a provider, set these three DNS records. They're what make the difference between your emails landing in Inbox vs Spam. All three are DNS-level configuration.

SPF — "who is allowed to send mail as my domain"

A single TXT record at your domain's apex:

example.com.    TXT    "v=spf1 include:sendgrid.net include:mail.crystalregistry.com ~all"

Lists the mail servers authorised to send as @example.com. ~all means "soft-fail everything else" (mark suspicious, still deliver). Start here; later you can move to -all (hard-fail).

Only ONE SPF record per domain — you cannot have two. If you add SendGrid to an existing SPF, modify the existing record.

DKIM — cryptographic signing of your emails

DKIM adds a digital signature to every outbound email's headers. Receiving servers verify the signature against a public key you publish in DNS. Any tampering invalidates the signature.

Each provider gives you a CNAME or TXT record pair:

s1._domainkey.example.com.    CNAME    s1.domainkey.u123.wl.sendgrid.net.
s2._domainkey.example.com.    CNAME    s2.domainkey.u123.wl.sendgrid.net.

Add them; activate DKIM in the provider's dashboard.

DMARC — how receivers should treat SPF / DKIM failures

_dmarc.example.com.    TXT    "v=DMARC1; p=none; rua=mailto:[email protected]"

Starts in p=none (monitor-only). After a few weeks of aggregated reports confirming legitimate traffic passes SPF + DKIM, graduate to p=quarantine (move failures to spam) and eventually p=reject (bounce failures outright).

Our DNS panel on all Domain India hosting plans supports all three record types. See How to Change Your Domain DNS Settings.

PHP + SendGrid

bash
composer require sendgrid/sendgrid
php
<?php
require __DIR__ . '/vendor/autoload.php';

$email = new \SendGrid\Mail\Mail();
$email->setFrom('[email protected]', 'Your Company');
$email->setSubject('Your password reset');
$email->addTo('[email protected]', 'User Name');
$email->addContent('text/plain', "Click here to reset: https://yourdomain.com/reset/token-abc");
$email->addContent('text/html',   "<p>Click here: <a href='https://yourdomain.com/reset/token-abc'>Reset password</a></p>");

$sendgrid = new \SendGrid($_ENV['SENDGRID_API_KEY']);

try {
    $response = $sendgrid->send($email);
    // 202 = accepted for delivery
    error_log("SendGrid status: " . $response->statusCode());
} catch (\Exception $e) {
    error_log("SendGrid error: " . $e->getMessage());
}

PHP + PHPMailer (provider-agnostic SMTP)

Works with any SMTP provider — cPanel, SendGrid's SMTP endpoint, SES SMTP, Postmark SMTP. Useful when you want one library for everything.

bash
composer require phpmailer/phpmailer
php
<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;

require __DIR__ . '/vendor/autoload.php';

$mail = new PHPMailer(true);

try {
    $mail->isSMTP();
    $mail->Host       = $_ENV['SMTP_HOST'];         // smtp.sendgrid.net, email-smtp.us-east-1.amazonaws.com, mail.yourdomain.com
    $mail->SMTPAuth   = true;
    $mail->Username   = $_ENV['SMTP_USER'];
    $mail->Password   = $_ENV['SMTP_PASS'];
    $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
    $mail->Port       = 465;

    $mail->setFrom('[email protected]', 'Your Company');
    $mail->addAddress('[email protected]', 'User Name');
    $mail->Subject = 'Your password reset';
    $mail->isHTML(true);
    $mail->Body    = '<p>Click here: <a href="https://yourdomain.com/reset/token">Reset</a></p>';
    $mail->AltBody = 'Click: https://yourdomain.com/reset/token';

    $mail->send();
} catch (\Exception $e) {
    error_log("Mail error: {$mail->ErrorInfo}");
}

Node.js + SendGrid

bash
npm install @sendgrid/mail
javascript
import sgMail from '@sendgrid/mail';
sgMail.setApiKey(process.env.SENDGRID_API_KEY);

const msg = {
  to: '[email protected]',
  from: '[email protected]',
  subject: 'Your password reset',
  text: 'Click here to reset: https://yourdomain.com/reset/token',
  html: '<p>Click here: <a href="https://yourdomain.com/reset/token">Reset</a></p>',
};

try {
  await sgMail.send(msg);
} catch (err) {
  console.error('SendGrid error:', err.response?.body || err.message);
}

Node.js + AWS SES (via nodemailer)

bash
npm install nodemailer @aws-sdk/client-ses
javascript
import nodemailer from 'nodemailer';
import { SES } from '@aws-sdk/client-ses';

const ses = new SES({ region: 'ap-south-1' });
const transporter = nodemailer.createTransport({ SES: { ses } });

await transporter.sendMail({
  from: '[email protected]',
  to:   '[email protected]',
  subject: 'Your password reset',
  text: 'Click here: https://yourdomain.com/reset/token',
  html: '<p>Click here: <a href="https://yourdomain.com/reset/token">Reset</a></p>',
});

ap-south-1 is Mumbai — lowest latency for Indian-based senders. Your SES account must be verified there (separate from other regions).

Webhook events — bounces, opens, clicks

Every reputable provider can POST to your server when events happen. Use these to:

  • Maintain a list of bad-bounce addresses (don't keep sending to them)
  • Measure engagement
  • Detect unsubscribes for marketing compliance
  • Detect when the user clicked the verification link (for conversion tracking)

SendGrid Event Webhook

  1. Dashboard → Settings → Mail Settings → Event Webhook
  2. Point to https://yourdomain.com/webhooks/sendgrid
  3. Select events: Delivered, Bounced, Deferred, Dropped, Processed
  4. Optional: enable signed webhooks for authenticity

Payload is an array of events:

json
[
  {
    "email": "[email protected]",
    "event": "bounce",
    "timestamp": 1713800000,
    "reason": "Mailbox full"
  }
]

Handle bounce events by marking the address in your DB as deliverable=false.

AWS SES via SNS

SES publishes events to an SNS topic. You subscribe an HTTP endpoint or a Lambda. More plumbing but more power — handles volume well.

Cost comparison at 10,000 emails / month

ProviderCost
cPanel SMTPIncluded in hosting
SendGrid Essentials$15 / mo (40,000 limit — cheap per email)
AWS SES$1 / month — cheapest at this volume
Mailgun Foundation$35 / mo (50,000 limit)
Postmark$15 / mo (10,000 limit)

At 100,000 emails / month:

ProviderCost
SendGrid Pro$90 / mo
AWS SES$10 / mo
Mailgun$75 / mo
Postmark$100 / mo

SES is typically the cheapest at volume. SendGrid has the easiest developer experience. Postmark has the highest price and the highest deliverability — pay for it on critical emails only.

Common pitfalls

  1. Forgetting to verify the sender domain / address. Every provider requires you prove ownership before sending. Verification = DNS records or click-link in an email.
  2. Multiple conflicting SPF records. There can only be one. If you add a provider, merge into the existing record.
  3. Missing DKIM. SPF alone is not enough — many providers (Gmail, Office 365) drop emails that pass SPF but fail DKIM.
  4. DMARC without DKIM fails. DMARC policies require either SPF or DKIM to pass with alignment. Set up DKIM first.
  5. Wildcard "From" addresses. [email protected] is fine. [email protected] depends on your mail server accepting any sender. Some email providers have strict sender-address requirements — check before deploying.
  6. Running marketing and transactional on the same provider. Marketing bounces / complaints hurt transactional deliverability. Use separate accounts or separate providers (transactional: Postmark / SES; marketing: Mailchimp / Brevo).
  7. Rate limits on shared IPs. A sudden burst looks like spam — ramp up gradually over the first 2 weeks on a new provider.
  8. Not handling bounces. After 5 hard bounces, a provider flags your account. After 20, they suspend sending. Maintain a suppression list in your own database and never send to permanently-bounced addresses.

Frequently asked questions

Can I send from `[email protected]` using my app?

No, not through most providers. They require you own and verify the sending domain. Use [email protected] with SendGrid's or SES's DKIM setup.

Why do my password-reset emails sometimes go to spam?

Most likely: missing DKIM, missing DMARC, sender reputation (new domain / new IP), or the content triggers spam filters. Run your test email through mail-tester.com — it gives a detailed score out of 10 with fixes.

Do I need a dedicated IP?

For < 50,000 emails / month — no, shared IPs are fine. For high-volume marketing — maybe, to isolate your reputation. Dedicated IPs need constant volume to stay "warm" — low volume on a dedicated IP is worse than medium volume on a shared IP.

How do I test emails without spamming myself?

Use Mailtrap.io (smtp.mailtrap.io) for development — captures every outgoing email in a virtual inbox, never actually sends. Switch to real provider in production.

Can I send emails from `localhost` during development?

Locally, use Mailhog or Mailtrap — they catch and display emails without delivering. On shared hosting, use your cPanel SMTP account as the dev endpoint.

What about Gmail's "bulk sender" requirements that launched in 2024?

Senders of > 5,000 emails / day to Gmail must have SPF + DKIM + DMARC aligned, one-click unsubscribe headers, spam-rate < 0.3%, and proper TLS. All good providers handle this; verify your DMARC is configured correctly.

Do transactional emails need an unsubscribe link?

Legally in India (CAN-SPAM-equivalent): transactional emails are exempt from unsubscribe requirements. In practice: include one anyway — it protects your sender reputation when users mark as spam. Marketing emails absolutely require unsubscribe by law (IT Rules + state-level consumer protection).


Need help configuring SPF / DKIM / DMARC for your Domain India-hosted domain? [email protected]. Our team can help verify your DNS is set up correctly for your chosen provider.

Was this article helpful?

Your feedback helps us improve our documentation

Still need help? Submit a support ticket