Skip to content
Pathbound DOCS

Gmail

These endpoints send a one-to-one email to a contact through Gmail, from a mailbox you connected to Pathbound. You can send now, schedule a send up to 30 days ahead, or save a draft and send it later. Every email that goes out is recorded on the contact’s timeline.

A draft or a scheduled email is held by Pathbound, not by Gmail. It does not appear in Gmail until it is sent, and you review it through this API.

Three things have to be in place first: an API key with the actions:write scope, a Gmail mailbox connected by the user who created that key, and sending switched on for that mailbox by a workspace admin. Before you send explains each. The MCP tools gmail_send_email, gmail_create_draft, gmail_send_draft, gmail_list_outbox and gmail_cancel_outbox_email call the same endpoints: see MCP tools.

EndpointScopeWhat it does
GET /v1/integrations/gmail/sendersactions:writeLists your mailboxes and whether each one can send.
POST /v1/contacts/:contact_id/send_emailactions:writeSends an email to a contact now.
POST /v1/integrations/gmail/outboxactions:writeSaves a draft, or schedules an email.
GET /v1/integrations/gmail/outboxactions:writeLists your drafts and your scheduled, sent, failed and canceled emails.
POST /v1/integrations/gmail/outbox/:id/sendactions:writeSends a draft now, or schedules it.
DELETE /v1/integrations/gmail/outbox/:idactions:writeDiscards a draft or cancels a scheduled email.

The scope. Every endpoint on this page needs actions:write. It is opt-in: a key holds it only when it was selected for that key. See Scopes.

The mailbox is your own. An API key acts as the user who created it, and sends only from Gmail mailboxes that user connected. A teammate’s mailbox is never listed and never usable. Connecting a mailbox takes a workspace admin, under Integrations → Gmail in the dashboard, so create the key as an admin who has connected one. An admin can connect a default mailbox and up to five more.

The mailbox can send. A mailbox sends when all three of these hold:

  1. Its connection is valid.
  2. Its Google permission includes sending (gmail.send).
  3. A workspace admin has turned on Allow agents to send from this mailbox for it on the Gmail page. The switch is per mailbox and is off until an admin turns it on.

The API checks all three on every request that sends, schedules or saves an email, and again when a scheduled email goes out. The switch applies to every caller: an API key is held to it exactly as an AI agent is. Listing your senders, listing the outbox and canceling an email do not need it.

from names the mailbox, by its address. It has to be one of your own connected mailboxes.

Without from, Pathbound chooses in this order:

  1. Your default mailbox, if it can send.
  2. Otherwise, the only one of your mailboxes that can send.
  3. If several can send and none of them is the default, the request is refused with SENDER_REQUIRED and you have to pass from.

If none of your mailboxes can send, the request is refused with the reason your default mailbox cannot.

A mailbox you name is used or refused. It is never swapped for another one. The same holds for a draft or a scheduled email: the mailbox is fixed when the email is created, and the email goes out from that mailbox or not at all.

These answers come from every endpoint that sends, schedules or saves an email. Each carries available_senders, the addresses you can send from right now, and a hint.

StatuscodeMeaningWhat to do
422SENDER_REQUIREDNo from was given, several of your mailboxes can send, and none of them is the default.Pass from as one of available_senders.
422SENDER_NOT_CONNECTEDfrom is not a mailbox you connected. A teammate’s mailbox gets this answer too.Use one of available_senders.
412integration_missingNo Gmail mailbox is connected in the workspace.A workspace admin connects one.
412no_primaryYou have not connected a Gmail mailbox of your own.Connect one, or use a key that belongs to a user who has.
412invalid_credentialsThe mailbox’s connection is no longer valid.Reconnect the mailbox.
412scope_missingThe mailbox’s Google permission does not include sending. The body carries needs_reconsent: true.Reconnect the mailbox and grant sending.
412tool_disabledSending is switched off for the mailbox. The body carries tool_disabled: true.A workspace admin turns on Allow agents to send from this mailbox.
{
"status": "error",
"error": "Several of your Gmail mailboxes can send and none is the default: pass from",
"code": "SENDER_REQUIRED",
"available_senders": ["[email protected]", "[email protected]"],
"hint": "Pass from as one of available_senders (GET /v1/integrations/gmail/senders lists your mailboxes)."
}
GET /v1/integrations/gmail/senders

Required scope: actions:write

Returns the Gmail mailboxes you connected and whether each one can send. Call it to choose a value for from.

Terminal window
curl https://api.pathbound.ai/v1/integrations/gmail/senders \
-H "Authorization: Bearer $PATHBOUND_API_KEY"
{
"status": "success",
"senders": [
{ "email": "[email protected]", "is_default": true, "send_enabled": true },
{ "email": "[email protected]", "is_default": false, "send_enabled": false, "reason": "tool_disabled" }
],
"default_sender": "[email protected]"
}
FieldDescription
sendersYour own mailboxes, the default first. Empty when you have connected none.
senders[].send_enabledWhether the mailbox can send right now.
senders[].reasonPresent when send_enabled is false: invalid_credentials, scope_missing or tool_disabled, with the meanings given above.
default_senderThe mailbox a request without from sends from. null when you have to pass from, or when no mailbox can send.
POST /v1/contacts/:contact_id/send_email

Required scope: actions:write

Sends one email to a contact, now. The recipient is the address on the contact record: no field of the request names one.

ParameterTypeDescription
contact_idstringThe contact to email.
HeaderRequiredDescription
Idempotency-KeyyesA key of your choosing for this one email. See Sending the same email twice.
FieldTypeRequiredDescription
subjectstringyesThe subject line. At most 998 characters, with no line breaks.
bodystringyesThe email’s content.
body_formatstringnohtml (the default) or text.
fromstringnoThe address of the mailbox to send from. See Which mailbox sends.
ccstring[]noAddresses to copy.
bccstring[]noAddresses to copy without showing them.
thread_idstringnoThe Gmail thread to reply in. See Replying in a thread.
include_signaturebooleannoAppend your saved email signature. Default false.

This endpoint only sends now. A request that carries send_at or draft is refused with 400 USE_OUTBOX and nothing is sent: use the outbox for those.

Terminal window
curl -X POST https://api.pathbound.ai/v1/contacts/665f1c2ab3d4e5f601234567/send_email \
-H "Authorization: Bearer $PATHBOUND_API_KEY" \
-H "Idempotency-Key: 7c1f0d5e-3b0a-4a3e-9a51-2f6f6e0f8f11" \
-H "Content-Type: application/json" \
-d '{
"from": "[email protected]",
"subject": "Next steps after the pilot",
"body": "<p>Hi Ada,</p><p>Here is the plan we talked about.</p>"
}'
{
"status": "success",
"action_id": "670bd0e4a1b2c3d4e5f60718",
"message_id": "19286f3a7c2d4e10",
"thread_id": "19286f3a7c2d4e10",
"sent_at": "2026-10-13T14:02:11.482Z",
"sender": "[email protected]"
}
FieldDescription
action_idPathbound’s record of this send.
message_id, thread_idGmail’s ids for the sent message and the thread it is in.
senderThe mailbox the email went out from.
replayedtrue when this answer repeats an earlier send under the same Idempotency-Key. Nothing was sent again.

The send appears on the contact’s timeline as a pathbound_action event with data.action_type set to gmail_send_email.

To reply inside an existing conversation, pass its thread_id. GET /v1/contacts/:contact_id/email-threads lists a contact’s Gmail threads with their ids.

A Gmail thread belongs to one mailbox, so the reply has to go out from the mailbox that holds the thread. A thread_id from another mailbox is refused with 422 THREAD_NOT_IN_MAILBOX and nothing is sent.

Idempotency-Key is what keeps a retry from becoming a second email. For 24 hours, a request with a key you have already used is answered like this:

Earlier request with this keyAnswer
Sent200 with the same message_id and replayed: true. Nothing is sent.
Still being processed409 ACTION_IN_PROGRESS. Ask again with the same key.
Refused by Gmail, so certainly not sentThe request is carried out as a new send.
Got no answer from Gmail502 SEND_UNCONFIRMED again, with replayed: true. Nothing is sent.

The general rules are under Idempotency.

StatuscodeMeaningWhat to do
400USE_OUTBOXThe body carries send_at or draft.Use the outbox endpoint.
400IDEMPOTENCY_KEY_REQUIREDThe Idempotency-Key header is missing.
400noneA field is not valid. details names it.
404noneNo such contact in your workspace.
409ACTION_IN_PROGRESSA request with this key is still being processed.Ask again with the same key.
412, 422see aboveThe mailbox cannot be used.
422CONTACT_EMAIL_MISSINGThe contact has no email address.
422THREAD_NOT_IN_MAILBOXthread_id is not a thread of the sending mailbox.Send from the mailbox that holds the thread, or leave thread_id out.
502GMAIL_SEND_FAILEDGmail refused the request or could not be reached. Nothing was sent.Retry with the same key after retry_after_seconds.
502SEND_UNCONFIRMEDGmail did not answer. The email may have been sent.Do not retry. See When Gmail does not answer.

Delivery is at most once. Pathbound hands an email to Gmail one time, and never resends one whose outcome it does not know.

When Gmail takes the request and then gives no clear answer (a timeout, a dropped connection, a server error after the email was handed over), the email may or may not have gone out. The answer is 502 with the code SEND_UNCONFIRMED:

{
"status": "error",
"error": "Gmail did not answer; the email may have been sent",
"code": "SEND_UNCONFIRMED",
"action_id": "670bd0e4a1b2c3d4e5f60718",
"sender": "[email protected]",
"hint": "Check the Sent folder of [email protected] before sending again. Do not retry this request; to send deliberately, use a new Idempotency-Key."
}

What to do with it:

  1. Do not retry automatically. The same request with the same Idempotency-Key gets SEND_UNCONFIRMED again for 24 hours and sends nothing.
  2. Look in the Sent folder of the sender mailbox.
  3. If the email is not there and you still want it sent, send it again under a new Idempotency-Key.

GMAIL_SEND_FAILED is the other 502 and means the opposite: nothing was sent, and a retry with the same key is safe. It carries retry_after_seconds. SEND_UNCONFIRMED never does.

A draft or a scheduled email whose send got no answer ends with status: "failed" and failure.code: "SEND_UNCONFIRMED". It is not sent again.

POST /v1/integrations/gmail/outbox

Required scope: actions:write

Saves an email that Pathbound holds: a draft that waits until you send it, or an email that goes out at send_at. The mailbox and the contact are checked now, with the same errors as Send an Email, so an email that could never be sent is refused when you create it.

The mailbox and the recipient’s address are fixed at this point. See What is checked when a scheduled email goes out.

HeaderRequiredDescription
Idempotency-KeyyesA key of your choosing for this one email. The same key within 24 hours returns the email as it is now, with replayed: true, and creates nothing. One exception: a scheduled request repeated after its send_at is more than 5 minutes in the past is answered 400 SEND_AT_IN_PAST, because the time is checked first. List the outbox to see what became of the email.
FieldTypeRequiredDescription
contact_idstringyesThe contact the email is for.
draftbooleanone of the twotrue to save a draft. Nothing is sent until you send the draft.
send_atstringone of the twoWhen to send. See below.
subject, body, body_format, from, cc, bcc, thread_id, include_signatureThe same fields as Send an Email. subject and body are required.

Pass exactly one of draft: true and send_at.

send_at is an ISO 8601 date and time with an explicit offset or Z, for example 2026-10-20T09:00:00+02:00 or 2026-10-20T07:00:00Z. A time without an offset is refused, because the server cannot know your time zone. It can be at most 30 days ahead. A time up to 5 minutes in the past is accepted and treated as now. Anything earlier is refused.

Terminal window
curl -X POST https://api.pathbound.ai/v1/integrations/gmail/outbox \
-H "Authorization: Bearer $PATHBOUND_API_KEY" \
-H "Idempotency-Key: 2b6a9c4e-11d7-4f0b-8a3c-5e9d7f1c2a40" \
-H "Content-Type: application/json" \
-d '{
"contact_id": "665f1c2ab3d4e5f601234567",
"from": "[email protected]",
"subject": "Next steps after the pilot",
"body": "<p>Hi Ada,</p><p>Here is the plan we talked about.</p>",
"send_at": "2026-10-20T09:00:00+02:00"
}'

A scheduled email is answered with 202, a draft with 201 Created, and a repeated request with 200 OK and replayed: true. The body has the same shape each time.

{
"status": "success",
"action_id": "670bd1a2a1b2c3d4e5f60730",
"email": {
"outbox_id": "670bd1a2a1b2c3d4e5f60731",
"status": "scheduled",
"contact_id": "665f1c2ab3d4e5f601234567",
"sender": "[email protected]",
"subject": "Next steps after the pilot",
"body_format": "html",
"cc_count": 0,
"bcc_count": 0,
"include_signature": false,
"send_at": "2026-10-20T07:00:00.000Z",
"attempts": 0,
"created_by": { "agent_id": "api_key:9f3c21ab" },
"send_requested_by": { "agent_id": "api_key:9f3c21ab" },
"action_id": "670bd1a2a1b2c3d4e5f60730",
"created_at": "2026-10-13T14:05:38.120Z",
"updated_at": "2026-10-13T14:05:38.120Z"
}
}

A scheduled email goes out within about 30 seconds after send_at.

You can hold at most 200 open emails (drafts plus scheduled) at a time. There is no endpoint that edits a held email: cancel it and create a new one.

StatuscodeMeaning
400IDEMPOTENCY_KEY_REQUIREDThe Idempotency-Key header is missing.
400DRAFT_OR_SEND_ATThe body carries both draft: true and send_at, or neither.
400SEND_AT_INVALIDsend_at is not an ISO 8601 date and time with an offset.
400SEND_AT_TOO_FARsend_at is more than 30 days ahead.
400SEND_AT_IN_PASTsend_at is more than 5 minutes in the past.
400noneAnother field is not valid. details names it.
404noneNo such contact in your workspace.
409ACTION_IN_PROGRESSA request with this key is still being processed.
412, 422see aboveThe mailbox cannot be used.
422CONTACT_EMAIL_MISSINGThe contact has no email address.
422CONTACT_EMAIL_INVALIDThe contact’s email is not a usable address.
422OUTBOX_LIMITYou already hold 200 open emails. Send or cancel some first.

Every outbox endpoint returns the email in this shape. A field with no value is left out.

FieldDescription
outbox_idThe email’s id, for the send and cancel endpoints.
statusdraft, scheduled, sending, sent, failed or canceled. See below.
contact_id, toThe contact, and the address the email was written for.
senderThe mailbox it goes out from.
subject, body_formatAs given when the email was created.
cc_count, bcc_countHow many addresses are copied. The addresses themselves are returned only by the list, with include_body=true.
reply_thread_idThe Gmail thread the email replies in, when one was given.
include_signatureWhether your signature is appended.
send_atWhen a scheduled email goes out.
expires_atDrafts only: when the draft expires.
attempts, next_attempt_atHow many times sending was tried, and when it is tried next after a failure that can be retried.
message_id, thread_id, sent_atSet once the email is sent: Gmail’s ids for the message and its thread, and the time.
failure{ code, message } when a send did not go through. See Failure codes.
created_byWhich credential created the email. For an API key, agent_id is api_key: followed by the key’s id.
send_requested_byWhich credential asked for it to be sent: set at creation for a scheduled email, and when a draft is sent.
action_idPathbound’s record of the latest decision about this email.
created_at, updated_atTimestamps.

Statuses

statusMeaning
draftHeld. It is never sent until you send it. A draft is kept for 30 days. At expires_at it becomes canceled, with failure.code set to DRAFT_EXPIRED.
scheduledGoes out at send_at, or at next_attempt_at after a failure that can be retried.
sendingBeing sent right now. It can no longer be canceled.
sentSent.
failedNot sent, or sent with an unknown outcome. failure says which.
canceledDiscarded, canceled or expired.

Once an email is sent, failed or canceled, Pathbound removes its body and its cc and bcc addresses. The sent copy is in Gmail.

Deleting a contact deletes the emails held for that contact.

GET /v1/integrations/gmail/outbox

Required scope: actions:write

Returns your own outbox emails, newest first. You never see a teammate’s.

ParameterTypeDefaultDescription
statusstringnoneOnly emails in this status: draft, scheduled, sending, sent, failed or canceled.
contact_idstringnoneOnly emails for this contact.
limitnumber50How many to return (1 to 200).
include_bodybooleanfalseAdd body, cc and bcc to each email that still holds them: drafts and scheduled emails.
afterstringnoneThe outbox_id of the last email you received. The list starts right after it: pass the last email of one page to read the next.
{
"status": "success",
"counts": { "total": 1, "draft": 1, "scheduled": 0, "sending": 0, "sent": 0, "failed": 0, "canceled": 0 },
"has_more": false,
"emails": [
{
"outbox_id": "670bd0e4a1b2c3d4e5f60719",
"status": "draft",
"contact_id": "665f1c2ab3d4e5f601234567",
"sender": "[email protected]",
"subject": "Next steps after the pilot",
"body_format": "html",
"cc_count": 1,
"bcc_count": 0,
"include_signature": false,
"expires_at": "2026-11-12T14:02:11.482Z",
"attempts": 0,
"created_by": { "agent_id": "api_key:9f3c21ab" },
"action_id": "670bd0e4a1b2c3d4e5f60718",
"created_at": "2026-10-13T14:02:11.482Z",
"updated_at": "2026-10-13T14:02:11.482Z",
"body": "<p>Hi Ada,</p><p>Here is the plan we talked about.</p>",
"cc": ["[email protected]"],
"bcc": []
}
]
}

subject and body are returned exactly as they will be sent.

counts gives how many emails the list holds in each status and in total. It counts every email that matches contact_id, whatever status, limit and after say. has_more is true when more emails follow the last one returned: pass that email’s outbox_id as after to read them.

POST /v1/integrations/gmail/outbox/:id/send

Required scope: actions:write

Sends a draft now, or schedules it when the body carries send_at. The email goes out from the mailbox the draft was written for, to the address it was written for. If that mailbox can no longer send, or the contact’s email has changed since, the request is refused and nothing is sent.

ParameterTypeDescription
idstringThe draft’s outbox_id.
HeaderRequiredDescription
Idempotency-KeyyesA key of your choosing for this one send.
FieldTypeRequiredDescription
send_atstringnoWhen to send, in the format given for creating a scheduled email. Leave it out to send now.

Sent now:

{
"status": "success",
"action_id": "670bd3c7a1b2c3d4e5f60742",
"message_id": "19286f9b1e5a7c33",
"thread_id": "19286f9b1e5a7c33",
"sent_at": "2026-10-13T14:20:03.915Z",
"sender": "[email protected]",
"email": {
"outbox_id": "670bd0e4a1b2c3d4e5f60719",
"status": "sent"
}
}

email is the full outbox email object, shortened here.

With send_at, the answer is 202 Accepted with status, action_id and email, and email.status is scheduled. You also get 202 for a send without send_at when the email was picked up for sending at the same moment: read email.status, and list the outbox to see how it ended.

A repeated request with the same Idempotency-Key returns 200 with replayed: true and the email as it is now, and sends nothing. Read email.status in that answer: an email whose send got no answer is replayed with status: "failed".

  • Gmail refused it, or could not be reached. The draft is kept as a draft, with the reason in failure, and you can send it again. The answer is 502 GMAIL_SEND_FAILED with retry_after_seconds, or 422 THREAD_NOT_IN_MAILBOX.
  • Gmail did not answer. The email becomes failed and the answer is 502 SEND_UNCONFIRMED. It is not sent again. See When Gmail does not answer.

These three answers carry email, the email as it is after the attempt.

StatuscodeMeaningWhat to do
400IDEMPOTENCY_KEY_REQUIREDThe Idempotency-Key header is missing.
400SEND_AT_INVALID, SEND_AT_TOO_FAR, SEND_AT_IN_PASTsend_at is not usable.
404noneNo such email in your outbox. Another user’s email gets this answer too.
409OUTBOX_NOT_DRAFTThe email is no longer a draft. current_status says what it is.
412, 422see aboveThe draft’s mailbox can no longer be used.Fix the mailbox, or cancel the draft and create one for another mailbox.
422RECIPIENT_CHANGEDThe contact’s email is no longer the address the draft was written for.Cancel the draft and create a new one.
422CONTACT_EMAIL_MISSING, CONTACT_EMAIL_INVALIDThe contact has no usable email address now.
422THREAD_NOT_IN_MAILBOXThe draft’s thread_id is not a thread of its mailbox.Cancel the draft and create one without thread_id.
502GMAIL_SEND_FAILEDNothing was sent. The draft is kept.Send it again after retry_after_seconds.
502SEND_UNCONFIRMEDGmail did not answer. The email may have been sent.Do not resend before checking the Sent folder.
DELETE /v1/integrations/gmail/outbox/:id

Required scope: actions:write

Discards a draft or cancels a scheduled email. Only an email in status draft or scheduled can be canceled.

ParameterTypeDescription
idstringThe email’s outbox_id.
{
"status": "success",
"email": {
"outbox_id": "670bd1a2a1b2c3d4e5f60731",
"status": "canceled"
}
}

email is the full outbox email object, shortened here.

StatuscodeMeaning
404noneNo such email in your outbox.
409OUTBOX_NOT_PENDINGThe email is already sending, sent, failed or canceled. current_status says which. An email that is sending or sent cannot be recalled.

What is checked when a scheduled email goes out

Section titled “What is checked when a scheduled email goes out”

A scheduled email is sent long after the request that asked for it, so Pathbound checks again at that moment, in this order:

  1. Whoever asked for the send may still send. The user is still a verified member of the workspace. For an API key: the key is still active, has not expired, and still holds actions:write. For an AI agent: its connection has not been disconnected.
  2. The mailbox chosen when the email was created can still send. It is used or refused, never swapped for another mailbox.
  3. The contact’s email is still the address the email was written for.

A check that fails ends the email as failed, with the reason in failure. Nothing is sent.

failure.code on an outbox email is one of these:

failure.codeMeaning
PRINCIPAL_REVOKEDWhoever asked for the send may no longer send: the user left the workspace, the API key was revoked, expired or lost actions:write, or the AI agent’s connection was disconnected.
SENDER_UNAVAILABLEThe mailbox can no longer send. failure.message names the reason.
RECIPIENT_CHANGEDThe contact’s email changed after the email was written.
CONTACT_EMAIL_MISSING, CONTACT_EMAIL_INVALIDThe contact has no usable email address now.
THREAD_NOT_IN_MAILBOXThe thread to reply in is not in the sending mailbox.
GMAIL_SEND_FAILEDGmail refused the email or could not be reached, and nothing was sent. A scheduled email is tried up to 3 times, 5 minutes apart, before it ends with this code.
SEND_UNCONFIRMEDGmail did not answer. The email may have been sent, and it is not sent again. Check the Sent folder.
DRAFT_EXPIREDA draft was not sent within 30 days. Its status is canceled.