API reference
The PineLead Email API sends your product's transactional email — sign-up confirmations, receipts, notifications — from a domain you have verified, and lets you follow each message after it leaves. Outreach to leads is not sent through the API; it goes from your connected mailbox in the dashboard.
Base URL#
All requests and responses are JSON. Send Content-Type: application/json with every request that has a body.
Authentication#
Every endpoint needs two headers: your account ID and your API key. Both are in the dashboard under Settings → API.
- The key is shown once, when you generate it. Only a fingerprint is kept after that, so store it somewhere safe.
- An account has one key. Regenerate API key issues a new one and revokes the old one immediately.
- Keep the key on your server. Anyone who has it can send email from your verified domains.
A request missing either header, or with a key that doesn't match the account, returns 401 Unauthorized.
Responses and errors#
A successful response wraps its payload in an envelope with a message and a data field:
Errors carry a message and no data:
400— the body failed validation.messageis a list of the problems, for example"each value in to must be an email". Unknown fields are dropped rather than rejected.401— missing or invalid credentials.404— the resource doesn't exist on your account.422— a send was refused. The body has a machine-readablecodeas well as amessage; see POST /v1/emails.429— you hit the rate limit.
Rate limits#
Sending is limited per account to 100 requests per minute on POST /v1/emails. The window starts with your first request and resets after 60 seconds. Past the limit you get a 429:
There is no Retry-After header; wait for the window to reset and retry. One request can carry up to 50 recipients.
GET/v1/me
Return the authenticated account's ID and email. Useful for checking your credentials.
POST/v1/emails
Accept one email for delivery. The message is recorded and queued straight away, and you get its ID back without waiting for delivery.
| Name | Type | Description |
|---|---|---|
| fromrequired | string | "jane@acme.com" or "Jane <jane@acme.com>". The domain must be verified on your account. Up to 320 characters. |
| torequired | string | string[] | One address or a list of addresses. |
| subjectrequired | string | Up to 998 characters. |
| html | string | HTML body. |
| text | string | Plain-text body. Provide html, text, or both — at least one is required. |
| cc | string | string[] | One address or a list. |
| bcc | string | string[] | One address or a list. Bcc recipients never appear in the headers other recipients see. |
| replyTo | string | A single address for replies. |
| headers | object | Extra headers as name–value strings, for example In-Reply-To to continue a thread. |
| tags | string[] | Up to 10 free-form labels, stored with the message and returned with it. |
- At most 50 recipients across
to,ccandbcccombined. Duplicate addresses are removed first. - Addresses on your suppression list — earlier hard bounces and spam complaints — are dropped from the message and the rest are sent. If no
toaddress is left, the send is refused.
Accepted means recorded and queued, not yet delivered. Follow the message with GET /v1/emails/:id or its events.
The code is one of:
unverified_domain— thefromdomain isn't verified on your account.invalid_from—fromisn't a usable address.missing_content— neitherhtmlnortextwas given.no_recipients— no usabletoaddress.too_many_recipients— more than 50 recipients in total.all_recipients_suppressed— everytoaddress is on your suppression list.no_mailbox_connected— only for sends from the dashboard, which go through your mailbox. API sends never return it.
See A send was rejected for how to fix each one.
GET/v1/emails
List your most recent messages, newest first.
| Name | Type | Description |
|---|---|---|
| limit | number | How many to return. Defaults to 50, at most 100. |
There is no cursor or filter yet — the list is always the latest messages on your account. It includes emails sent from the dashboard as well as through the API; source tells them apart.
| Name | Type | Description |
|---|---|---|
| _id | string | The message ID. |
| userId | string | Your account ID. |
| projectId | string | Set on emails sent from a project in the dashboard. |
| source | string | api or app (sent from the dashboard). |
| fromEmail, fromName | string | The sender, split into address and display name. |
| to, cc, bcc | string[] | Who the message was sent to, after duplicates and suppressed addresses were removed. |
| replyTo, subject, html, text, headers, tags | — | As sent. |
| status | string | queued, sending, sent, delivered, bounced, complained or failed. |
| rfcMessageId | string | The Message-ID header. Quote it in In-Reply-To to reply in the same thread. |
| attempts | number | How many times delivery was attempted. |
| lastError | string | Why delivery failed, when it did. |
| queuedAt, sentAt, deliveredAt | date | When each stage happened. |
| openedAt, clickedAt | date | The first open and first click, when reported. |
| createdAt, updatedAt | date | Record timestamps. |
sent means the message left PineLead; delivered, bounced and complained are what the receiving server reported afterwards.
GET/v1/emails/:id
Fetch one message on your account by its ID. The data is a single message object, with the fields listed above.
Returns 404 with "Email not found" if the message doesn't exist on your account, and 400 if the ID isn't well-formed.
GET/v1/emails/:id/events
The delivery timeline of one message, oldest first — up to 100 events.
| Name | Type | Description |
|---|---|---|
| type | string | queued, sent, delivered, bounced, complained, failed, opened or clicked. |
| occurredAt | date | When it happened. |
| recipient | string | The address the event is about, when there is one. |
| detail | string | Context, such as a bounce's diagnostic code or a clicked link. |
| bounceKind | string | permanent or transient, on bounces. Only a permanent bounce suppresses the address. |
opened and clicked are reported by the recipient's email client, and privacy proxies and link scanners trigger them too, so treat them as hints. They never change a message's status. A permanent bounce or a spam complaint adds the address to your suppression list.
GET/v1/domains
List the sending domains on your account, oldest first, so you can check which from addresses you may use. Adding and verifying a domain is done in the dashboard.
| Name | Type | Description |
|---|---|---|
| domain | string | The bare domain, lowercased. |
| status | string | pending (waiting for DNS), verified or failed. Only a verified domain can send. |
| records | object[] | The DNS records to publish: type (TXT or CNAME), name, value, purpose (verification, dkim or dmarc), ttl, and required — false for the recommended DMARC record. |
| verifiedAt, lastCheckedAt | date | When it was verified, and last checked. |
| lastError | string | Why the last check didn't pass, when it didn't. |