> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tetherai.ca/llms.txt
> Use this file to discover all available pages before exploring further.

# Send an email message

> Sends an email through the authenticated user's connected mailbox (Gmail or Outlook). The mailbox is resolved by matching the `from` address against the user's connected accounts — the user must have previously completed the OAuth flow for that address. Supports plain-text or HTML bodies, CC/BCC, threading (replies), and attachments stored in R2.



## OpenAPI

````yaml /api-reference/openapi.yaml post /api/email/messages/send
openapi: 3.0.3
info:
  title: Tether Relay and Tether API
  version: 1.0.0
  description: >-
    Route-derived OpenAPI contract generated from tether-relay backend sources.
    Regenerate with scripts/sync-openapi-from-backend.mjs.
servers:
  - url: https://your-instance.example.com
    description: Production
  - url: http://localhost:2212
    description: Local API service
security: []
tags:
  - name: AI
  - name: Applications
  - name: Approval
  - name: Auth
  - name: Automations
  - name: Calls
  - name: Campaigns
  - name: Contact Analytics
  - name: Contact Metrics
  - name: Contact Notes
  - name: Contacts
  - name: Conversation Monitor
  - name: Conversations
  - name: Departments
  - name: Email
  - name: Escalation Dashboard
  - name: Event Logs
  - name: Google Sheets
  - name: Marketplace
  - name: Messages
  - name: Notification Sounds
  - name: Notifications
  - name: Org Snapshots
  - name: Organizations
  - name: Outreach
  - name: Outreach Metrics
  - name: Outreach Sources
  - name: Pipelines
  - name: Processes
  - name: Prompts
  - name: Reminders
  - name: Sms
  - name: Templates
  - name: Traces
  - name: Training
  - name: Upload
  - name: User Snapshots
  - name: Users
  - name: Vertical
  - name: Webchats
  - name: Webhook
paths:
  /api/email/messages/send:
    post:
      tags:
        - Email
      summary: Send an email message
      description: >-
        Sends an email through the authenticated user's connected mailbox (Gmail
        or Outlook). The mailbox is resolved by matching the `from` address
        against the user's connected accounts — the user must have previously
        completed the OAuth flow for that address. Supports plain-text or HTML
        bodies, CC/BCC, threading (replies), and attachments stored in R2.
      operationId: post_email_messages_send
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailSendMessageRequest'
      responses:
        '200':
          description: >-
            Email sent. Returns the stored message document, conversation id,
            and provider thread id.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmailSendMessageResponse'
        '400':
          description: >-
            Missing required fields, invalid `from` format, or attempted to send
            a private note through this endpoint.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmailErrorResponse'
        '404':
          description: >-
            No active mailbox is connected for the given `from` address on the
            authenticated user.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmailErrorResponse'
        '500':
          description: Unexpected failure while sending through the email provider.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmailErrorResponse'
      security:
        - bearerAuth: []
components:
  schemas:
    EmailSendMessageRequest:
      type: object
      description: >-
        Payload to send an email through a connected mailbox. The server
        resolves which mailbox to send from by matching `from` (and optionally
        `provider`) against the authenticated user's connected email accounts.
      properties:
        from:
          type: string
          format: email
          description: >-
            Email address of the sending mailbox. Must belong to a mailbox the
            authenticated user has connected via the OAuth flow (Gmail or
            Outlook). Example: `aneesh@tetherai.ca`.
          example: aneesh@tetherai.ca
        provider:
          type: string
          enum:
            - gmail
            - outlook
          description: >-
            Tie-breaker used only when the same `from` address is connected
            under more than one provider for the same user. Omit in the common
            case — the server resolves it automatically.
          example: gmail
        to:
          oneOf:
            - type: string
              format: email
            - type: array
              items:
                type: string
                format: email
          description: >-
            Recipient address, or an array of recipient addresses. Each entry
            may be a bare email (`user@x.com`) or RFC 5322 formatted (`"Name"
            <user@x.com>`).
          example:
            - recipient@example.com
        cc:
          type: array
          items:
            type: string
            format: email
          description: Optional CC recipients. Same format rules as `to`.
        bcc:
          type: array
          items:
            type: string
            format: email
          description: Optional BCC recipients. Same format rules as `to`.
        subject:
          type: string
          description: >-
            Subject line. Optional — when omitted on a reply, the server reuses
            the subject from the conversation; when omitted on a new thread,
            `(no subject)` is used.
          example: Weekly report
        body:
          type: string
          description: >-
            Message body. Plain text by default; set `isHtml: true` to send as
            HTML. Required.
          example: Hi — here is the report you asked for.
        isHtml:
          type: boolean
          default: false
          description: When `true`, `body` is sent as `text/html` instead of `text/plain`.
        threadId:
          type: string
          description: >-
            Join key to continue an existing email thread. For Gmail this is the
            Gmail `threadId`; for Outlook it is the `conversationId`. Both are
            normalised to the same field.


            **New email:** omit this field. The server creates a new
            conversation and the provider assigns a brand-new thread.


            **Reply:** pass the thread id of the conversation you want to
            continue. The provider appends the reply to the same thread, and the
            server auto-fills `inReplyTo` / `references` from the latest message
            in that thread.


            **How to obtain it:**

            1. From the response of the first `POST /api/email/messages/send` —
            the body returns `threadId`. Persist it for future replies.

            2. From `GET /api/conversation` (or `/api/conversation/:id`) — the
            conversation's `channelIdentifier` field holds the email thread id
            for email-channel conversations.

            3. From a stored message — each email message carries
            `emailMetadata.threadId` on the Message document.


            Recipient address alone is **not** enough to continue a thread,
            because the same recipient can have multiple unrelated threads.
          example: gthread-1902a9f3c4b1e0d5
        inReplyTo:
          type: string
          description: >-
            RFC 5322 `In-Reply-To` header — the `Message-ID` of the message
            being replied to (with angle brackets). Used for threading in
            recipient clients. If omitted on a reply, the server fills it from
            the latest message in the thread.
        references:
          type: string
          description: >-
            RFC 5322 `References` header — space-separated `Message-ID`s forming
            the reply chain. If omitted on a reply, the server fills it from the
            latest message in the thread.
        isInternal:
          type: boolean
          description: >-
            Reserved. Must be `false` or omitted on this endpoint. Private notes
            are posted through the messages endpoint, not the email send
            endpoint. Sending `true` here returns `400`.
        attachments:
          type: array
          description: >-
            File attachments to include. Each entry must supply either
            `storageKey` (preferred — an R2 storage key produced by the upload
            endpoint) or a pre-signed `url` the server can GET.
          items:
            type: object
            properties:
              storageKey:
                type: string
                description: >-
                  R2 storage key returned by the upload endpoint. Preferred over
                  `url`. When provided, the server generates a signed URL
                  internally and streams the bytes to the provider.
              url:
                type: string
                format: uri
                description: >-
                  HTTPS URL the server can fetch the attachment bytes from. Use
                  only when `storageKey` is not available (e.g. re-sending an
                  attachment referenced by external URL).
              filename:
                type: string
                description: >-
                  File name shown to the recipient. Defaults to `attachment` if
                  omitted.
              contentType:
                type: string
                description: >-
                  MIME type (e.g. `application/pdf`). If omitted, the server
                  infers it from `filename`.
              size:
                type: number
                description: >-
                  Size of the attachment in bytes. Informational; the server
                  measures the actual stream.
            additionalProperties: true
      required:
        - from
        - to
        - body
      additionalProperties: true
      example:
        from: ops@acme.example
        to:
          - jamie.lee@example.com
        subject: Weekly report
        body: Hi Jamie — attaching this week's report.
        isHtml: false
    EmailSendMessageResponse:
      type: object
      description: Success response for `POST /api/email/messages/send`.
      properties:
        message:
          $ref: '#/components/schemas/EmailMessage'
        conversationId:
          type: string
          description: >-
            Internal id of the conversation the message was appended to (or
            created, on a new thread). Use this with `GET /api/conversation/:id`
            to fetch the full conversation.
        threadId:
          type: string
          description: >-
            Provider-side thread id for this email (Gmail `threadId` / Outlook
            `conversationId`). **Save this** — pass it back as `threadId` on
            subsequent `POST /api/email/messages/send` calls to continue the
            same thread.
          example: gthread-1902a9f3c4b1e0d5
      required:
        - message
        - conversationId
      additionalProperties: true
      example:
        message:
          _id: 665f1a0c0e0a4b001a2c9f60
          channelType: email
          status: delivered
          body: Hi Jamie — attaching this week's report.
        conversationId: 665f1a0c0e0a4b001a2c9f70
        threadId: gthread-1902a9f3c4b1e0d5
    EmailErrorResponse:
      type: object
      description: >-
        Error envelope returned by Email feature endpoints. Most paths only set
        `error`; a few include `code` for diagnostic context (e.g. provider
        mismatches).
      properties:
        error:
          type: string
        message:
          type: string
        code:
          type: string
      required:
        - error
      additionalProperties: true
      example:
        error: Email account not found
    EmailMessage:
      type: object
      description: >-
        Stored email message document (the `message` field on the send
        response).
      properties:
        _id:
          type: string
          description: Internal message id (MongoDB ObjectId).
        organizationId:
          type: string
        conversationId:
          type: string
          description: Conversation the message belongs to.
        contactId:
          type: string
          description: Contact (recipient) the conversation is bound to.
        userId:
          type: string
          description: Owning user.
        channelType:
          type: string
          enum:
            - email
          description: Always `email` for this endpoint.
        isIncoming:
          type: boolean
          description: Always `false` for messages produced by this endpoint (outbound).
        isInternal:
          type: boolean
          description: >-
            Always `false` on this endpoint. Private notes use a different
            route.
        status:
          type: string
          enum:
            - pending
            - sending
            - queued
            - dispatched
            - delivered
            - failed
            - rejected
            - aborted
          description: Delivery status. `delivered` on a successful send.
        body:
          type: string
          description: >-
            Plain-text body persisted on the message (parsed from the provider
            response).
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/EmailAttachmentStored'
        emailMetadata:
          $ref: '#/components/schemas/EmailMessageEmailMetadata'
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      additionalProperties: true
      example:
        _id: 665f1a0c0e0a4b001a2c9f60
        organizationId: 64ee9a8b1e7f2a0011223344
        conversationId: 665f1a0c0e0a4b001a2c9f70
        contactId: 665f1a0c0e0a4b001a2c9f01
        userId: 64ee9a8b1e7f2a0011223399
        channelType: email
        isIncoming: false
        isInternal: false
        status: delivered
        body: Hi Jamie — attaching this week's report.
        attachments: []
        createdAt: '2026-05-18T12:34:56.000Z'
        updatedAt: '2026-05-18T12:34:56.000Z'
    EmailAttachmentStored:
      type: object
      description: An attachment stored on a sent/received email message.
      properties:
        url:
          type: string
          description: >-
            R2 storage key (for uploads) or URL of the attachment as persisted
            on the message.
        originalUrl:
          type: string
          description: >-
            Signed URL used to deliver the attachment to the provider at send
            time. Short-lived.
        contentType:
          type: string
          description: MIME type.
        size:
          type: number
          description: Size in bytes.
        filename:
          type: string
          description: File name shown to the recipient.
      additionalProperties: true
      example:
        url: org/64ee9a8b1e7f2a0011223344/attachments/report.pdf
        contentType: application/pdf
        size: 184320
        filename: report.pdf
    EmailMessageEmailMetadata:
      type: object
      description: >-
        Email-specific fields attached to a Message when `channelType` is
        `email`.
      properties:
        accountId:
          type: string
          description: >-
            Internal id of the EmailAccount that sent or received this message.
            Not part of the public API surface — use `from` to identify the
            mailbox.
        externalMessageId:
          type: string
          description: >-
            Provider-side message id (Gmail `id` or Outlook `id`). Use together
            with the provider to re-fetch the raw message.
        from:
          type: string
          format: email
          description: Sender address.
        to:
          type: array
          items:
            type: string
            format: email
          description: Recipient addresses.
        cc:
          type: array
          items:
            type: string
            format: email
        bcc:
          type: array
          items:
            type: string
            format: email
        snippet:
          type: string
          description: >-
            Short plain-text preview of the message body, produced by the
            provider.
        subject:
          type: string
          description: >-
            Subject line of this specific email message. May differ from the
            conversation subject if the subject changed mid-thread.
        bodyHtml:
          type: string
          description: HTML body of the message (if available).
        headers:
          $ref: '#/components/schemas/EmailMessageHeaders'
      additionalProperties: true
      example:
        accountId: 665f1a0c0e0a4b001a2c9f50
        externalMessageId: 1902a9f3c4b1e0d5
        from: ops@acme.example
        to:
          - jamie.lee@example.com
        cc: []
        bcc: []
        snippet: Hi Jamie - attaching this week's report...
        subject: Weekly report
    EmailMessageHeaders:
      type: object
      description: RFC 5322 header fields captured on the message.
      properties:
        messageId:
          type: string
          description: RFC 5322 `Message-ID` header of this email (with angle brackets).
        inReplyTo:
          type: string
          description: RFC 5322 `In-Reply-To` header.
        references:
          type: string
          description: RFC 5322 `References` header (space-separated Message-IDs).
      additionalProperties: true
      example:
        messageId: <CAEvDmF0xyz@mail.gmail.com>
        inReplyTo: <CAEvDmF0abc@mail.gmail.com>
        references: <CAEvDmF0abc@mail.gmail.com> <CAEvDmF0def@mail.gmail.com>
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

````