Send Email

The core endpoint for sending emails via Tratto

POST /v1/emails is the core endpoint for sending emails. Send a simple transactional email, use a template, schedule for later, or attach files.

Prerequisites

Before you can send emails, you need a verified sending domain. See Domains for setup.

Minimal Send

Send a basic email with just the required fields.

cURL

curl -X POST https://api.tratto.email/v1/emails \
  -H "Authorization: Bearer tratto_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "[email protected]",
    "to": "[email protected]",
    "subject": "Welcome!",
    "text": "Hello, this is a test email."
  }'

Node.js

const response = await fetch('https://api.tratto.email/v1/emails', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer tratto_live_...',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    from: '[email protected]',
    to: '[email protected]',
    subject: 'Welcome!',
    text: 'Hello, this is a test email.',
  }),
});
const { data } = await response.json();
console.log('Email sent:', data.id);

Python

import requests

response = requests.post(
  'https://api.tratto.email/v1/emails',
  headers={
    'Authorization': 'Bearer tratto_live_...',
    'Content-Type': 'application/json',
  },
  json={
    'from': '[email protected]',
    'to': '[email protected]',
    'subject': 'Welcome!',
    'text': 'Hello, this is a test email.',
  },
)
data = response.json()['data']
print(f"Email sent: {data['id']}")

Response:

{
  "data": {
    "id": "email_abc123xyz",
    "status": "queued",
    "from": "[email protected]",
    "to": "[email protected]",
    "subject": "Welcome!",
    "text": "Hello, this is a test email.",
    "createdAt": "2025-06-30T12:00:00Z"
  }
}

Send with HTML

Include both text (fallback) and html (rendered content):

curl -X POST https://api.tratto.email/v1/emails \
  -H "Authorization: Bearer tratto_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "[email protected]",
    "to": "[email protected]",
    "subject": "Welcome!",
    "text": "Hello, this is a test email.",
    "html": "<h1>Hello!</h1><p>This is a test email.</p>"
  }'

Email clients show html if supported; fall back to text otherwise.

Depending on your plan, Tratto may append a footer to the HTML version — the "Sent using Tratto" badge, mandatory on some plans (today Free) and optional on the others. See Email Footer & Branding.

Send with Markdown

Pass a markdown body instead of html and Tratto renders it server-side into responsive, email-safe HTML (plus a plain-text part) using the Markdown format:

curl -X POST https://api.tratto.email/v1/emails \
  -H "Authorization: Bearer tratto_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "[email protected]",
    "to": "[email protected]",
    "subject": "Welcome!",
    "markdown": "# Welcome, {{firstName}}!\n\nThanks for signing up.\n\n[Open your dashboard](https://app.yourdomain.com){button}",
    "variables": {"firstName": "Alice"}
  }'

Rules and behavior:

  • markdown and html are mutually exclusive — sending both fails with 422 ("markdown and html are mutually exclusive — provide one or the other.").
  • At least one of html, text, markdown, or templateId is required, and an empty markdown fails with 422 ("markdown must not be empty.").
  • The markdown is rendered once, at request time; the resulting HTML and text are stored on the email, so delivery retries never re-render.
  • An explicit text field wins over the render-generated plain-text part.
  • {{variables}} are substituted at delivery, after the render — same as every other send.
  • Raw HTML inside the markdown is escaped to literal text, and javascript: / data: links are never linkified. See Markdown Templates for the syntax and the security model.

Send with a Template

Instead of composing HTML in code, use a template with variable substitution.

Step 1: Create a template (see Templates)

{
  "name": "Welcome Email",
  "html": "<h1>Hello {{firstName}}!</h1><p>Your code: {{code}}</p>"
}

A template holds a name and a body. It has no subject — that belongs to each send.

Step 2: Send using the template

curl -X POST https://api.tratto.email/v1/emails \
  -H "Authorization: Bearer tratto_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "[email protected]",
    "to": "[email protected]",
    "subject": "Welcome, Alice!",
    "templateId": "tmpl_abc123",
    "variables": {
      "firstName": "Alice",
      "code": "SECRET123"
    }
  }'

subject is required on every send. The subject and the template's html (and, for a Markdown template, its pinned text part) are all rendered with the variables you pass; a variable you don't pass becomes an empty string.

Schedule an Email

Send an email at a future time using the scheduledAt field (ISO 8601 format):

curl -X POST https://api.tratto.email/v1/emails \
  -H "Authorization: Bearer tratto_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "[email protected]",
    "to": "[email protected]",
    "subject": "Reminder!",
    "text": "See you tomorrow.",
    "scheduledAt": "2025-07-01T09:00:00Z"
  }'

The email will be queued and sent at the specified time (must be in the future).

Add Attachments

Include files as base64-encoded attachments:

curl -X POST https://api.tratto.email/v1/emails \
  -H "Authorization: Bearer tratto_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "[email protected]",
    "to": "[email protected]",
    "subject": "Invoice",
    "text": "See attached invoice.",
    "attachments": [
      {
        "filename": "invoice.pdf",
        "contentType": "application/pdf",
        "content": "JVBERi0xLjQKJeLj..."
      }
    ]
  }'

Steps to attach a file:

  1. Read the file as binary
  2. Base64-encode the content
  3. Include filename, contentType, and content in the attachments array

JavaScript example:

const fs = require('fs');

const fileBuffer = fs.readFileSync('invoice.pdf');
const base64Content = fileBuffer.toString('base64');

const attachments = [
  {
    filename: 'invoice.pdf',
    contentType: 'application/pdf',
    content: base64Content,
  },
];

Tags for Organization

Add tags to organize and filter emails:

curl -X POST https://api.tratto.email/v1/emails \
  -H "Authorization: Bearer tratto_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "from": "[email protected]",
    "to": "[email protected]",
    "subject": "Order Confirmation",
    "text": "Your order has been confirmed.",
    "tags": ["order", "transactional"]
  }'

Later, filter emails by tags or use them in webhooks.

Full Request Body Reference

FieldTypeRequiredDescription
fromstringSender email address (must be from a verified domain)
tostringRecipient email address
ccstring[]Carbon copy recipients
bccstring[]Blind carbon copy recipients
subjectstring✓ (if not using template)Email subject line
textstringPlain text content (used as fallback)
htmlstringHTML content (mutually exclusive with markdown)
markdownstringMarkdown, rendered server-side (mutually exclusive with html, max 100,000 chars)
templateIdstringTemplate ID (instead of subject/text/html)
variablesobjectVariables to interpolate in template
attachmentsarrayArray of attachment objects
scheduledAtstring (ISO 8601)Schedule email for future delivery
tagsstring[]Organization tags (max 10)
replyTostringReply-to address
headersobjectCustom email headers (advanced)

Response

Success (2xx):

{
  "data": {
    "id": "email_abc123xyz",
    "status": "queued",
    "from": "[email protected]",
    "to": "[email protected]",
    "cc": [],
    "bcc": [],
    "subject": "Welcome!",
    "createdAt": "2025-06-30T12:00:00Z",
    "scheduledAt": null
  }
}

Error (4xx):

{
  "error": {
    "code": "FORBIDDEN",
    "message": "Domain 'mail.acme.com' is not verified for this tenant.",
    "docs": "https://docs.tratto.email/en/domains",
    "suggestion": "Add and verify the domain at https://app.tratto.email/domains before sending."
  }
}

Common Errors

FORBIDDEN: unverified domain

Cause: The from domain has not been verified yet. The API returns 403 with the domain named in message.

Fix: Complete the domain verification process.

RATE_LIMITED

Cause: More than 100 requests in one second with the same API key, or more than 500 requests in one minute from the same IP.

Fix: Implement exponential backoff and retry later. See Rate Limits.

VALIDATION_ERROR

Cause: Invalid request (missing required field, malformed JSON, etc.).

Fix: Check the error message and refer to the Error Codes reference.

Next Steps


Edit this page on GitHub

Last updated on