Docs
Sending emails

Sending emails

Sending emails through WeSend.

API Endpoint

Prerequisites

Steps

Bearer Token

Ensure you put your API Key as a Bearer token in the Authorization header of the HTTP request:

'Authorization': `Bearer YOUR_API_KEY`

HTTP Body

{
    "fromName": "Your Company",  // (optional) Display name for the sender
    "fromEmail": "hello@yourdomain.com",  // Sender email address
    "to": "recipient@email.com",  // Receiver address
    "subject": "Email sent from WeSend!",  // Subject for the email
    "html": "<h1>Yay! You got the email.</h1>",  // (optional) HTML format of the email body
    "text": "Yay! You got the email.",  // (optional) Text format of the email body
    "replyTo": "reply@yourdomain.com",  // (optional) Reply-to email address
    "cc": "cc@example.com",  // (optional) CC recipient(s), comma-separated
    "bcc": "bcc@example.com",  // (optional) BCC recipient(s), comma-separated
    "attachments": [  // (optional) Array of attachments
        {
            "filename": "invoice.pdf",  // Name of the file
            "content": "base64EncodedContent",  // Base64 encoded file content (optional if url is provided)
            "url": "https://example.com/file.pdf",  // URL to external file (optional if content is provided)
            "contentType": "application/pdf"  // (optional) MIME type
        }
    ]
}

API Endpoint

Send a POST Request to http://localhost:3000/api/send-email

Example Usage

💡 Pro Tip: For a better developer experience, consider using our WeSend SDK which provides type safety, error handling, and a cleaner API.

Send attachments from a remote file

Include the url parameter to send attachments from a remote file. This parameter accepts a URL to the file you want to attach. Define the file name that will be attached using the filename parameter.

curl -X POST http://localhost:3000/api/send-email \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fromName": "Your Company",
    "fromEmail": "hello@yourdomain.com",
    "to": "recipient@email.com",
    "subject": "Email with external attachments!",
    "html": "<h1>Please find the files attached.</h1>",
    "text": "Please find the files attached.",
    "attachments": [
      {
        "filename": "monthly-report.pdf",
        "url": "https://example.com/reports/monthly-report.pdf",
        "contentType": "application/pdf"
      },
      {
        "filename": "company-logo.png",
        "url": "https://example.com/images/logo.png",
        "contentType": "image/png"
      }
    ]
  }'

Send attachments from a local file

Include the content parameter to send attachments from a local file. This parameter accepts the Base64 encoded content of the file you want to attach. Define the file name that will be attached using the filename parameter.

curl -X POST http://localhost:3000/api/send-email \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fromName": "Your Company",
    "fromEmail": "hello@yourdomain.com",
    "to": "recipient@email.com",
    "subject": "Email with attachment from WeSend!",
    "html": "<h1>Please find the invoice attached.</h1>",
    "text": "Please find the invoice attached.",
    "attachments": [
      {
        "filename": "invoice.pdf",
        "content": "JVBERi0xLjQKJcOkw7zDtsOmCjUgMCBvYmoKPDwKL0ZpbHRlciAvRmxhdGVEZWNvZGUKL0xlbmd0aC...",
        "contentType": "application/pdf"
      }
    ]
  }'

Field Reference

Main Fields

FieldTypeRequiredDescription
fromNamestringNoDisplay name for the sender (e.g., "Your Company")
fromEmailstringYesSender email address
tostringYesRecipient email address
subjectstringYesEmail subject line
htmlstringNo*HTML content of the email
textstringNo*Plain text content of the email
replyTostringNoReply-to email address
ccstringNoCC recipient(s), comma-separated email addresses
bccstringNoBCC recipient(s), comma-separated email addresses
attachmentsarrayNoArray of attachment objects (see below)

*At least one of html or text is required.

Attachment Object Fields

FieldTypeRequiredDescription
filenamestringYesName of the attachment file
contentstringNo*Base64 encoded file content
urlstringNo*URL to external file (HTTP/HTTPS only)
contentTypestringNoMIME type (e.g., "application/pdf", "image/png")

*Either content or url is required, but not both.

Attachment Examples

// Base64 content (recommended for API usage)
{
  "filename": "invoice.pdf",
  "content": "JVBERi0xLjQKJcOkw7zDtsOmCjUgMCBvYmoKPDwK...",
  "contentType": "application/pdf"
}
 
// URL-based attachment (external file)
{
  "filename": "report.pdf",
  "url": "https://example.com/reports/monthly-report.pdf",
  "contentType": "application/pdf"
}
 
// Text attachment with base64
{
  "filename": "notes.txt",
  "content": "SGVsbG8gV29ybGQh",  // "Hello World!" in base64
  "contentType": "text/plain"
}
 
// Image attachment from URL
{
  "filename": "logo.png",
  "url": "https://example.com/images/company-logo.png",
  "contentType": "image/png"
}
 
// Mixed attachments (base64 + URL)
{
  "attachments": [
    {
      "filename": "invoice.pdf",
      "content": "base64EncodedPDF...",
      "contentType": "application/pdf"
    },
    {
      "filename": "receipt.jpg",
      "url": "https://example.com/receipts/12345.jpg",
      "contentType": "image/jpeg"
    }
  ]
}

Using the SDK Instead?

If you prefer a more developer-friendly experience with type safety, error handling, and simplified API calls, we provide official SDKs for multiple programming languages.

Available SDKs

  • JavaScript/TypeScript - Full TypeScript support, Node.js & browser compatibility
  • Python - Full type annotations, sync API, Python 3.x compatibility
  • Go - Go SDK (Coming Soon)
  • PHP - PHP SDK (Coming Soon)
  • Ruby - Ruby SDK (Coming Soon)

Benefits of Using an SDK

  • ✨ Type Safety - Full type definitions and IntelliSense
  • 🛡️ Error Handling - Proper error types and status codes
  • 🔧 Simplified API - No need to handle HTTP requests manually
  • 📎 Attachment Support - Easy file handling with base64 encoding
  • 🌐 Cross-Platform - Works in Node.js and browsers
  • ⚙️ Customizable - Support for custom base URLs

For complete SDK documentation, examples, and advanced usage, visit our SDK documentation.

Response

Success Response (200)

{
  "message": "Email sent successfully"
}

Error Responses

400 Bad Request - Missing Authorization Header

{
  "error": "Authorization header not found."
}

400 Bad Request - Invalid Authorization Format

{
  "error": "Invalid authorization header. Create a Bearer Token."
}

400 Bad Request - Missing API Key

{
  "error": "Invalid or missing API Key."
}

400 Bad Request - Missing Required Fields

{
  "error": "Missing required field 'fromEmail'."
}
{
  "error": "Missing required field 'to'."
}
{
  "error": "Missing required field 'subject'."
}
{
  "error": "Missing required field 'text' or 'html'."
}

400 Bad Request - Invalid replyTo Email Format

{
  "error": "Invalid 'replyTo' email format."
}

400 Bad Request - Inactive API Key

{
  "error": "This API key is currently inactive."
}

403 Forbidden - Invalid API Key

{
  "error": "Invalid API Key or no SMTP configuration found."
}

400 Bad Request - Invalid Attachment Configuration

{
  "error": "Attachment 'filename.pdf' must have either 'content' or 'url' field."
}
{
  "error": "Attachment 'filename.pdf' cannot have both 'content' and 'url' fields. Use either one."
}
{
  "error": "Attachment 'filename.pdf' has invalid URL protocol. Only HTTP and HTTPS are allowed."
}
{
  "error": "Attachment 'filename.pdf' has invalid URL format."
}
{
  "error": "Attachment 'filename.pdf' URL is not allowed. Internal/local URLs are blocked for security."
}
{
  "error": "Attachment 'filename.pdf' URL port is not allowed. Internal service ports are blocked for security."
}
{
  "error": "Attachment 'filename.pdf' URL is not allowed. Access to the hosting server is blocked for security."
}

500 Internal Server Error - Email Sending Failed

{
  "error": "Error sending email: [specific error message]"
}

500 Internal Server Error - URL Fetch Failed

{
  "error": "Error processing attachment 'filename.pdf': Failed to fetch attachment from URL: 404 Not Found"
}
{
  "error": "Error processing attachment 'filename.pdf': Timeout fetching attachment from URL (30s limit)"
}
{
  "error": "Error processing attachment 'filename.pdf': Attachment file too large: 30MB (max 25MB)"
}

502 Bad Gateway - Transport Error

{
  "error": "Could not create the transporter object."
}

Rate Limiting

Each API key may make 100 requests per 60 seconds. The limit is shared between POST /api/send-email and POST /api/emails, so using both endpoints draws down a single allowance.

Every response carries the current state of your allowance:

HeaderMeaning
RateLimit-LimitRequests permitted per window
RateLimit-RemainingRequests left in the current window
RateLimit-ResetSeconds until the window resets
Retry-AfterSeconds to wait. Sent only on a 429

Exceeding the limit returns 429 Too Many Requests:

{
  "error": "Too many requests. Please retry later."
}

Wait for the number of seconds given in Retry-After before retrying.

Configuring the limit

Self-hosted instances set the limit with two environment variables:

VariableDefaultMeaning
RATE_LIMIT_MAX100Requests per window, per key. 0 disables the limiter
RATE_LIMIT_WINDOW_MS60000Window length in milliseconds

The limit is counted per process, not across your whole deployment.

Counters live in the memory of the Node process that handles the request. If you run WeSend behind a load balancer, across several containers, or under a process manager that forks workers, each process keeps its own counters and the effective limit is roughly RATE_LIMIT_MAX multiplied by the number of processes. All counters also reset when the process restarts or redeploys.

Treat it as a safety valve that stops one runaway client from saturating your SMTP relay, not as an exact quota. If you need a hard global limit, put a shared limiter in front of WeSend, such as nginx limit_req, Cloudflare, or a Redis-backed limiter.

Two further details worth knowing:

  • Because the window is fixed rather than sliding, a client can send up to twice the limit across a window boundary, for example 100 requests at 59.9s and another 100 at 60.1s. For the purpose the limiter serves, that is not meaningfully different from the limit itself.
  • Requests with a missing or malformed Authorization header are rejected before the limiter runs and are not counted, because there is no key to attribute them to. They cost a header check and no database or SMTP work. Volumetric flood protection belongs in front of the application.

Common MIME Types

File ExtensionMIME Type
.pdfapplication/pdf
.jpg, .jpegimage/jpeg
.pngimage/png
.gifimage/gif
.txttext/plain
.htmltext/html
.csstext/css
.jsapplication/javascript
.jsonapplication/json
.xmlapplication/xml
.csvtext/csv
.zipapplication/zip
.xlsxapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.docxapplication/vnd.openxmlformats-officedocument.wordprocessingml.document
.pptxapplication/vnd.openxmlformats-officedocument.presentationml.presentation

Security Note

Important: WeSend supports two types of attachments:

  • Base64-encoded content: For direct file uploads (recommended for smaller files)
  • URL-based attachments: For external files accessible via HTTP/HTTPS URLs

Security Protections:

  • Server-side file paths are not supported
  • Internal/local URLs are blocked (localhost, private IPs, .local domains)
  • Access to the hosting server is blocked
  • Common internal service ports are blocked
  • Only public, external URLs are allowed

Best Practices

File Size Limits

  • Keep base64 attachments under 25MB for best performance
  • For larger files, consider using URL-based attachments instead
  • URL-based attachments can handle larger files since they're downloaded on-demand

Base64 Encoding

  • Always encode binary files as base64
  • Remove any data URL prefixes (e.g., data:application/pdf;base64,)
  • Use proper content type headers for better email client compatibility

URL-based Attachments

  • Ensure URLs are publicly accessible and return proper HTTP status codes
  • Use HTTPS URLs for security
  • URLs should return the actual file content, not redirects
  • File size limit: 25MB per attachment
  • Timeout limit: 30 seconds per URL fetch
  • URLs must be accessible from WeSend's servers
  • Security: Only external, public URLs are allowed (internal/local URLs are blocked)

Multiple Attachments

{
  "attachments": [
    {
      "filename": "invoice.pdf",
      "content": "base64EncodedPDF...",
      "contentType": "application/pdf"
    },
    {
      "filename": "receipt.jpg",
      "url": "https://example.com/receipts/12345.jpg",
      "contentType": "image/jpeg"
    },
    {
      "filename": "report.pdf",
      "url": "https://api.example.com/reports/monthly.pdf",
      "contentType": "application/pdf"
    }
  ]
}