Email API · REST + GraphQL · 5 typed SDKs · Idempotency Stripe-style

Transactional email API tuned for Latin mailbox routing. Built for engineering teams that ship past Resend, SendGrid, Postmark, Mailgun, AWS SES.

A transactional email API that competes against Resend on developer experience, Postmark on deliverability, SendGrid on volume scale, and Amazon SES on per-email cost is a fool's errand because each of those products is purpose-built for its category. EMP Email API competes on a different axis: Latin mailbox routing where the recipient mix is 60+ percent Latin Latam infrastructure, jurisdiction posture under Ley 81 with no CLOUD Act exposure, support workflows that operate natively in Spanish and Portuguese, and an SLA backed by 16 years of Panama-located operations. Pick this API when those four axes matter to your product. Use the catalog page below to verify whether they do or do not.

Latin inbox placement
97.8%
Independent benchmark, 60%+ Latin recipient mix
P95 latency Americas
<800ms
Median 220ms; APAC up to 2s
SDKs maintained
5 typed
Node TS · Python · PHP · Go · Ruby
Pricing entry
$99 + $0.55/1K
Setup $290 one-time · No mandatory onboarding

What this API is honest about

  • EMP loses to Amazon SES on raw per-email cost above 5 million monthly sends. SES at $0.10 per 1K cannot be matched by an operation with single-tenant infrastructure cost floor.
  • EMP loses to Resend on React Email developer experience. JSX-as-template is a Resend exclusive ecosystem; EMP SDKs do not match it.
  • EMP loses to Postmark on global transactional inbox placement when the recipient mix is US/EU-weighted. Postmark publishes 98.7 percent vs EMP 96.4 percent on global benchmarks; the gap is real.
  • EMP does NOT offer a permanent free tier. The minimum monthly commit is $99 base plus per-1K. Honest reason: free tier shared-IP traffic compromises paid customer reputation.
  • EMP wins for Latin-weighted recipient mix, jurisdiction-sensitive workloads, Spanish/Portuguese support workflows, and SLA backed by 16-year operational track record (4 SLA breaches 2024, 1 breach 2025, all credited per terms).
Two interfaces · REST and GraphQL · Pick the right one per call site

Two API surfaces. One backend. Pick per use case.

REST handles 90 percent of transactional email API call sites because the dominant pattern is "send this email, here is the body, return me the message ID." GraphQL exists for the analytics dashboard, the admin panel that composes 4 separate queries into a single round-trip, and the integration partner that needs to subscribe to a stream of events with field-level filtering. Both interfaces sit on top of the same backend; switching between them does not change anything about deliverability, authentication, idempotency semantics, or webhook signatures.

When · 90% of call sites

REST · /v1/email/send

Standard HTTP POST with JSON body. Bearer token auth. Idempotency-Key header. Returns 202 Accepted with message_id, queued_at timestamp, and expected webhook event types. Errors follow RFC 7807 Problem Details with error code, retry-after hint where applicable, and human-readable message in English or Spanish based on Accept-Language.

  • Auth: Bearer API key in Authorization header
  • Idempotency: Idempotency-Key header with UUID v4
  • Retry semantics: Server replays response on duplicate key within 24 hours
  • Rate limit: 100 req/sec default, burst 200; Enterprise custom
  • SDKs: 5 typed SDKs handle this transparently
// Example: send a password reset transactional email
POST /v1/email/send HTTP/1.1
Host: api.emailmarketingpanama.com
Authorization: Bearer emp_live_a1b2c3d4...
Content-Type: application/json
Idempotency-Key: 9f4b2e1a-7c3d-4e89-b1f6-2a8d5c0e9b3f

{
  "from": "alerts@yourdomain.com",
  "to": ["user@example.com"],
  "subject": "Reset your password",
  "template_id": "password_reset_v3",
  "vars": { "user_name": "Maria", "reset_url": "..." },
  "tags": ["transactional", "auth", "reset"]
}
When · Analytics + admin panels

GraphQL · /v1/graphql

Single round-trip composition for dashboards that need 4 different data shapes simultaneously. Strongly typed schema with full introspection. Subscriptions over WebSocket for live event streams. Persisted queries supported for production deployment to bypass query parsing overhead. Schema is published openly at /v1/graphql/schema for client codegen with graphql-codegen, Apollo Codegen, gqlgen, or equivalent tooling.

  • Schema: 47 types, 23 query roots, 8 mutations, 4 subscriptions
  • Persisted queries: Supported via Apollo persistedQuery extension
  • Subscriptions: WebSocket transport, idle disconnect 5 minutes
  • Rate limit: Query complexity cost-based, 1,000 cost units/sec
  • Codegen: Schema introspection enabled in non-production
# Example: dashboard query for last 24h send stats by template
POST /v1/graphql HTTP/1.1
Host: api.emailmarketingpanama.com

{
  sendsByTemplate(
    since: "2026-05-09T00:00:00Z",
    until: "2026-05-10T00:00:00Z"
  ) {
    template_id
    name
    delivered
    bounced
    open_rate
    p95_latency_ms
  }
}
Five typed SDKs · OpenAPI 3.1 source of truth · 6-week release cadence

SDKs maintained by EMP engineering, not auto-generated and forgotten.

SDK quality differentiates email API products in 2026. Resend ships clean TypeScript SDKs because its team obsesses over DX. SendGrid ships SDKs that span 7 languages but show their age (callbacks instead of promises in some, missing TypeScript definitions in others). Mailgun ships SDKs that work but use HTTP Basic Auth and form-encoded bodies that feel dated. EMP ships 5 SDKs maintained directly by engineering with shared release cadence, full typing, and an OpenAPI 3.1 spec that is the canonical source of truth so generating a custom client in any other language is a 30-minute exercise.

Node.js / TypeScript
npm install @emp/email-sdk
  • Language: TypeScript-first; .d.ts ships with package
  • Runtime: Node 18+, Deno 1.40+, Bun 1.0+
  • Type inference: Full request and response inference from OpenAPI
  • Async pattern: Promise-based, AbortController support
  • Bundle size: 18 KB gzipped, zero runtime deps
Python
pip install emp-email-sdk
  • Type system: pydantic v2 models for all request/response
  • Runtime: Python 3.9+, asyncio support optional
  • Type checker: mypy strict, pyright strict mode pass
  • Async pattern: Both sync and async clients shipped
  • Compatibility: Django, FastAPI, Flask integration examples
PHP
composer require emp/email-sdk
  • Type system: Psalm-typed, PHPStan level 9 pass
  • Runtime: PHP 8.1+ enum support required
  • Framework hooks: Laravel service provider, Symfony bundle
  • Async pattern: ReactPHP support optional
  • Composer: Aligned with PSR-7 HTTP message standard
Go
go get github.com/emp-dev/email-sdk-go
  • Generation: Generated from OpenAPI 3.1 spec, hand-edited interfaces
  • Runtime: Go 1.21+ for slices and maps generics
  • Concurrency: context.Context first parameter, idiomatic cancellation
  • Idempotency: Auto-generates UUID v4 for retried requests
  • Testing: Mock server included for unit tests
Ruby
gem 'emp-email-sdk'
  • Type system: Sorbet-typed, RBS files shipped
  • Runtime: Ruby 3.0+, Rails 6.1+ supported
  • Framework hooks: ActionMailer adapter included
  • Async pattern: Threaded; Async gem optional
  • Testing: RSpec helpers included
Generate your own
openapi-generator-cli generate
  • Spec source: /v1/openapi.json, OpenAPI 3.1 compliant
  • Java: openapi-generator with java-okhttp4 client
  • .NET: openapi-generator with csharp-restsharp
  • Rust: openapi-generator with rust-reqwest
  • Elixir: openapi-generator with elixir client
Production primitives · Idempotency keys + HMAC webhooks + replay protection

Idempotency keys + HMAC-signed webhooks are the difference between demo and production.

A demo email API skips idempotency: the developer hits send, the server returns 200, the developer moves on. A production email API must handle the case where send returned a network timeout but the email was already accepted by the queue, the client retries, and a duplicate transactional email goes to the recipient. Idempotency keys solve this. Webhooks must be HMAC-signed because an unsigned webhook is a vector for any attacker who guesses the URL to inject false delivery events into the customer's analytics pipeline. EMP gives both, by default, with the same Stripe-style ergonomics that engineering teams already know.

Production primitive 1

Idempotency keys (Stripe-style)

Client passes Idempotency-Key header with a UUID v4 generated per logical send operation. If the request times out and the client retries with the same key within 24 hours, the original response replays without duplicating the send. SDKs auto-generate keys when the customer does not pass one explicitly; a derived key from the application's own business operation ID (order ID, user ID, OTP request ID) is the recommended pattern.

  • Window: 24-hour replay window per key
  • Storage: Server-side cached response, not just the message_id
  • Key format: UUID v4 standard; 36 chars; case-sensitive
  • Critical use cases: OTP delivery, payment receipts, password reset
  • Comparable products: Stripe payment intents, Postmark message-stream sends
// Example: idempotency key derived from order ID
const idempotencyKey = crypto.randomUUID();

const response = await fetch('https://api.emailmarketingpanama.com/v1/email/send', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer emp_live_...',
    'Idempotency-Key': idempotencyKey,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(payload)
});

// Retry with the same key returns the same message_id
// without sending a duplicate email
Production primitive 2

HMAC-SHA256 webhook signatures

Every webhook delivered to the customer endpoint carries an X-EMP-Signature header that contains a timestamp plus an HMAC-SHA256 hash of the payload using the customer's webhook secret. The receiving service verifies origin and rejects stale requests outside a 5-minute window to prevent replay attacks. Secret rotation is dual-key during the rotation window so the customer service can roll the secret without coordinated downtime.

  • Algorithm: HMAC-SHA256 over timestamp+payload
  • Replay window: Reject requests older than 5 minutes
  • Rotation: Dual-key 24-hour overlap; both old and new validate
  • Retry on 5xx: 6 attempts at 1m, 5m, 30m, 2h, 8h, 24h
  • Schema: CloudEvents 1.0 with EMP extensions
# Example webhook signature verification (Python)
import hmac, hashlib, time

def verify_emp_signature(payload: bytes, header: str,
                          secret: str) -> bool:
    timestamp, signature = parse_header(header)
    # Reject if older than 5 minutes (replay protection)
    if time.time() - int(timestamp) > 300:
        return False
    expected = hmac.new(
        secret.encode(),
        f'{timestamp}.{payload.decode()}'.encode(),
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)
Email API competitive landscape · 2026 · Honest comparison

Where EMP wins, ties, and loses against the seven email API providers buyers actually shortlist.

The seven providers below cover roughly 95 percent of email API procurement evaluations in 2026 according to procurement data from Vendr, G2, and direct customer interviews. Each row is a single dimension where the comparison is meaningful and the relative strength is documented. Stars (★) mark dimensions where the named competitor wins decisively for that use case.

Dimension EMP Email API Resend SendGrid Postmark Mailgun Amazon SES Mailtrap
Latin mailbox routing optimization Native, 60%+ Latam mix optimized None specific Generic global Generic global None specific None specific None specific
Inbox placement global benchmark 96.4% ~95-96% (Resend) 95.3% 98.7% ~95% 92-94% (mgmt-dependent) ~93% (varies)
Inbox placement Latam-weighted mix 97.8% Not measured separately Not measured separately Not measured separately Not measured separately Self-managed deliverability Not measured
React Email (JSX templates) Not native; integrate manually Native, first-class Not supported Not supported Not supported Not supported Not supported
SDKs maintained (typed) 5 (Node TS, Python, PHP, Go, Ruby) 3 (Node, Python, Go) 7 (legacy, mixed quality) 5 (PHP, Ruby, Python, Go, Node) 4 (Node, Python, Ruby, PHP) ~12 (AWS SDK ecosystem) 3 (Node, Python, Ruby)
Idempotency keys (Stripe-style) Yes, native No, manual workaround Partial via batch ID Yes via message-stream No, manual workaround SES MessageDeduplicationId No
HMAC webhook signature HMAC-SHA256 standard HMAC-SHA256 standard Ed25519 signed Basic auth (older), HMAC newer HMAC-SHA256 standard SNS message authentication Token-based, no HMAC
Free tier 2026 None; $99 base monthly 3,000/mo permanent free 60-day trial only (killed May 2025) 10-day trial 5,000/mo for 1 month 3,000/mo first 12 months 4,000/mo permanent free
Entry pricing (50K monthly emails) $99 + $27.50 = $126.50 $20 $19.95 $15 (only 10K incl) $35 $5 $15
5M monthly emails pricing $2,849 $1,500-$2,500 (custom) $2,500-$5,000 (Premier) $3,500+ (custom) $1,500+ (custom) $500 Custom
Dedicated IP entry threshold $640 platform tier (incl) $30/mo from Scale (500/day req.) $89.95 Pro tier (incl 1 IP) $50/mo (300K+ vol req.) $59-$79/mo (volume req.) $24.95/mo (BYOIP $0.10/h) Higher tiers only
Jurisdiction Panama Ley 81, no CLOUD Act USA (CLOUD Act exposure) USA (CLOUD Act, Twilio MSA) USA (CLOUD Act) USA + EU optional AWS region selectable USA
EU data residency Available on Enterprise (Frankfurt) Not available 2026 Not standard Not available Native EU option EU regions selectable EU available
Spanish/Portuguese support workflow Native, 24x5 GMT-5 English only English+ via outsourced English only English only English only (AWS Support) English only
SLA credit-back terms published Up to 25%, 6-month termination right Ent Enterprise tier only, custom 99.95% Pro, custom Premier Up to 10% Custom Enterprise AWS standard SLA None published
3 pricing tiers · Setup $290 one-time · No mandatory non-refundable onboarding

$99 base · $0.55 per 1K sends. Predictable invoicing for finance.

The pricing structure exists to align customer cost with EMP underlying cost: base subscription covers fixed infrastructure (queue isolation, dedicated reputation maintenance, support headcount allocation) and per-1K rate covers variable cost (bandwidth, compute, mailbox provider feedback processing). Bundled with platform tier, the catalog absorbs the API into the platform price. Stand-alone API as below.

Starter API

If your stack is shipping fewer than 200K transactional emails monthly and dedicated IP reputation is not yet a constraint.

$99 monthly base
+ $0.55 per 1K sends · shared IP pool
  • REST + GraphQL full access
  • 5 SDKs included
  • Idempotency keys + HMAC webhooks
  • Shared IP pool (Latam-routed)
  • Standard support, business hours GMT-5
  • SLA 99% monthly uptime
  • Month-to-month, no commitment
Enterprise API

If transactional volume exceeds 5M monthly, multi-region routing matters, or compliance documentation needs to be procurement-grade.

$2,400 monthly base
+ $0.30 per 1K sends · multiple dedicated IPs
  • All Pro features
  • 3 dedicated IPs with geo-routing
  • Custom rate limits (above 100 req/sec)
  • Dedicated technical account manager
  • SLA 99.9% quarterly with 25% credit-back
  • SOC 2 mapping under NDA · Q3 2026 Type II target
  • 24-month commit · audit rights desk + on-site biennial
6 questions engineering leads ask · Honest answers

What CTOs and platform leads ask on the technical evaluation call.

The questions below come up in roughly 70 percent of technical evaluation calls with engineering leadership. The honest answer is documented here so the evaluation can move past these to the actual decision factors (Latin routing fit, jurisdiction posture, SDK quality, SLA economics).

"Why no React Email native integration when the rest of the modern email API stack ships it?"

React Email is a Resend-led ecosystem because the library author and Resend share a team. The library has 1.35 million weekly npm downloads and is unquestionably the modern way to author transactional emails in JSX. EMP does not ship React Email native because integrating it into the SDK would mean a) maintaining a fork the Resend-led community moves faster than EMP can track, or b) adopting Resend's tooling as a dependency which adds a coupling EMP does not control. The honest path: if React Email matters to your team, render JSX templates client-side using react-email/render and pass the rendered HTML string to the EMP send call. Code path is identical to passing any other rendered HTML. render(MyEmail({props})) returns a string; pass it to the API. If the JSX-as-template ergonomics matter more than the Latin routing or jurisdiction, Resend is the honest recommendation.

"How does the Latin mailbox routing actually differ from generic global routing under the hood?"

Generic global routing assumes a single dispatch profile across all destinations: connection pooling, throttle policy, retry timing, TLS preference, and bounce classification are tuned for the median mailbox provider behavior across the global mix. Latin mailbox routing tunes those parameters per-recipient-domain when the recipient is on Latam-specific infrastructure. Three concrete differences: (1) connection pool sizing per Latam-region datacenter for Gmail, Microsoft, Yahoo accounts whose backend lookups land on Latin-resident servers (faster connection setup, lower TLS handshake latency); (2) throttle policy tuned to specific mailbox providers like Movistar, Claro, Tigo, Telmex regional infrastructure that have stricter per-sender per-hour limits than US/EU mailbox providers and bounce harder at the threshold; (3) bounce classification trained on Latam Spanish-language SMTP error responses that some mailbox providers return in localized text rather than RFC 3463 standard codes. The cumulative effect is roughly 1.4 percentage points of inbox placement on Latin-weighted mix vs generic routing. Not magic; just specific tuning.

"What happens to outstanding webhook deliveries if our endpoint goes down for 4 hours during a deploy?"

Webhook delivery uses retry-with-exponential-backoff on 5xx errors from the customer endpoint: 1 minute, 5 minutes, 30 minutes, 2 hours, 8 hours, 24 hours, total 6 attempts before declaring permanent delivery failure. A 4-hour endpoint outage during deploy will cause 3 to 4 retry attempts to fail and the 5th retry at 8 hours to succeed; no webhooks are lost, all are delivered eventually within the 24-hour total window. If the endpoint is down longer than 24 hours, the API surfaces the failed webhook events at /v1/webhooks/failures for manual replay; the customer can pull the queue and replay each event, or trigger a bulk replay via /v1/webhooks/replay with a date range. Bulk replay is rate-limited to 1,000 events per minute to avoid overwhelming the customer endpoint after recovery. Failed webhooks are retained 30 days on Pro tier, 90 days on Enterprise tier.

"Does the API support sending from multiple sender domains under a single account, or do we need separate accounts per brand?"

Multi-domain support is native at the API level. A single API account can authenticate any number of verified sender domains; the from address in the send call determines the dispatch domain. Domain verification requires DKIM record publication (1 record per domain) and SPF inclusion (one include statement per domain), both validated automatically every 6 hours by EMP infrastructure. Reputation isolation: separate sender domains share the underlying IP pool but maintain distinct reputation profiles per domain at the mailbox provider level. If domain A has a deliverability event, domain B reputation is not affected. For full IP reputation isolation across brands (separate dedicated IPs per brand), Pro or Enterprise tier with multiple dedicated IPs is the architecture. Comparable to SendGrid Subuser model and Postmark Server model; both also support multi-domain on a single account.

"What is the MTA underneath? PowerMTA, KumoMTA, custom build, AWS SES wrapper?"

EMP runs both PowerMTA 5.0 and KumoMTA 1.x in production as parallel dispatch infrastructure with traffic split based on send characteristics. PowerMTA handles bulk-marketing send patterns where the customer base is steady-state daily volume. KumoMTA handles transactional patterns where the request rate is spiky and the per-message latency budget is tight (sub-second to dispatch). Both MTAs run on EMP-owned dedicated bare-metal in a Tier III Panama City facility with secondary dispatch out of Miami. Not an AWS SES wrapper, not a Twilio/SendGrid reseller, not a Mailgun pass-through. Customer traffic terminates inside EMP-controlled infrastructure end to end. The About EMP page documents the Tier 1 ownership map for procurement validation.

"Can we self-host the API, or is it a hosted service only?"

Hosted service only. Self-hosted deployment is not offered. Three structural reasons: (1) Latin mailbox routing depends on EMP's IP reputation and feedback-loop subscriptions with Latam mailbox providers that cannot be transferred to a customer-operated installation; (2) Panama Scorer AI runs in EMP infrastructure with the training pipeline EMP operates; exporting the model weights without the pipeline degrades scoring quality within weeks as mailbox provider behavior drifts; (3) the SLA economics require shared infrastructure cost; a single-customer self-hosted install would require a $40K+ monthly retainer to sustain the same SLA which is outside the catalog. If self-hosted is the requirement, the honest recommendations are KumoMTA self-installed (EMP installation service available) or PowerMTA self-installed (EMP installation service available); either gives full control with EMP setup expertise on the deployment but the customer operates the runtime.

14-day technical evaluation. Sandboxed API access without affecting production reputation.

Technical evaluation covers full API access on isolated sandbox infrastructure, all 5 SDKs, sample webhook payloads for testing signature verification, and 60-minute call with EMP engineering for architecture review. The sandbox does not consume production IP reputation so the eval cannot harm a future deployment. Eval is free to qualified prospects with documented engineering use case.

Conversion calibration: 64% close after technical eval · 28% redirect to Resend/SendGrid/Postmark when fit is wrong · 8% extend eval period