Home / Docs
API documentation

Everything you need to build on WiseSender

One base URL, JSON over HTTPS. Verification and contacts use a verification key and verification credits, sending uses a sending key and email credits.

Overview

The base URL for every request is https://wisesender.com. Requests and responses are JSON, encoded as UTF-8. All endpoints allow cross-origin calls, but keep your keys on your server: a key in browser code can be copied by anyone.

EndpointWhat it doesBilled in
GET /v1/verifyCheck one addressVerification credits
POST /v1/verify/bulkCheck up to 1,000 addressesVerification credits
GET /v1/listsYour contact listsFree
POST /v1/lists/{id}/contactsAdd someone to a listFree, 1 verification credit with verify
POST /v1/email/sendSend transactional emailEmail credits

Authentication

Create keys in the dashboard. Each key is shown once; only a hash is stored, so copy it somewhere safe. You can hold up to 10 keys of each type and revoke any of them instantly.

Key typeStarts withSend it as
Verification key (verify and contacts)ws_live_X-API-Key header, Authorization: Bearer, or ?key= for verify
Sending keyws_send_Authorization: Bearer header, or X-API-Key
Prefer the header over the query string. URLs end up in server logs and browser history.

Verify one email

ParameterDescription
emailRequired. The address to check.
modepower (default) checks the mailbox with the receiving server and takes a few seconds. quick skips the mailbox and answers in under a second.
curl "https://wisesender.com/v1/[email protected]&mode=power" \
  -H "X-API-Key: ws_live_YOUR_KEY"

Verify in bulk

POST/v1/verify/bulk

Send up to 1,000 addresses in one request. Duplicates are removed before charging. The request waits until every address is checked, so allow a long timeout in power mode; for bigger lists, upload a file in the dashboard instead.

curl https://wisesender.com/v1/verify/bulk \
  -H "X-API-Key: ws_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "emails": ["[email protected]", "[email protected]"], "mode": "power" }'

The result object

FieldMeaning
statussafe, risky or invalid. The short answer.
categoryThe detailed verdict. See categories.
score0 to 100. Higher is more likely to be delivered.
reasonA plain-language explanation, e.g. "Mailbox does not exist".
safe_to_sendtrue for safe and role results.
syntaxThe address is well formed.
domain_existsThe domain resolves. null if DNS failed.
mx_foundThe domain publishes mail servers.
mx_recordsMail server host names, best first.
mailbox_existsThe server confirmed the inbox. null if not checked or not answered.
mailbox_fullThe inbox exists but is over quota.
catch_allThe domain accepts every address.
disposableA throwaway inbox provider.
role_accountA shared address such as info@ or sales@.
free_providerGmail, Outlook, Yahoo and similar.
suggestionThe likely intended domain for a typo, or null.
smtp_noteThe mail server's own reply, for debugging.
modeThe mode that was used.

Categories

categorystatusscoreWhat to do
safesafe90 to 95Send.
rolesafe75Send; expect lower engagement.
valid_domainrisky60Quick mode only. Run power mode before a campaign.
catch_allrisky55Send carefully, watch bounces.
unknownrisky40Retry later. Refunded in power mode.
inbox_fullrisky25Retry later.
disposablerisky10Don't send.
invalidinvalid0Remove from your list.

Lists and contacts

Add people to your contact lists from your own app, a CRM or an automation tool. Contacts added this way behave like a signup on your hosted form: lists with double opt-in email a confirmation first, and the list's welcome email goes out once they're subscribed.

GET/v1/lists

Returns your lists with their IDs: { "lists": [{ "id": 12, "name": "Newsletter", "subscribed": 2140, "double_optin": true }] }

POST/v1/lists/{id}/contacts
FieldDescription
emailRequired. The address to add.
nameOptional, up to 100 characters.
verifyOptional. true runs a quick check first and refuses typos, dead domains and throwaway inboxes with a 422. Uses 1 verification credit.
curl -X POST https://wisesender.com/v1/lists/12/contacts \
  -H "X-API-Key: ws_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]", "name": "Ann Lee", "verify": true }'

# 201 Created (200 if they were already on the list)
{ "email": "[email protected]", "status": "subscribed", "created": true }

status is subscribed, or pending when a confirmation email was sent.

GET/v1/lists/{id}/contacts?status=subscribed&limit=100&offset=0

Pages through a list. status can be subscribed, pending or unsubscribed; limit goes up to 1,000.

DELETE/v1/lists/{id}/contacts/{email}

Unsubscribes the address and adds it to your do-not-send list, so no campaign or API send reaches it again.

Account events

Besides the delivery events below, your webhook URL receives events about your audience. Automation tools like Zapier, Make and n8n can start a workflow from them.

EventWhen
contact.subscribedSomeone joined a list through a form, the API or by confirming
contact.unsubscribedSomeone used an unsubscribe link, or you removed them by API
list.verifiedAn uploaded list finished verifying
campaign.finishedA campaign was sent, or stopped
{
  "event": "contact.subscribed",
  "timestamp": "2026-11-11T09:14:03.120Z",
  "data": { "list_id": 12, "list": "Newsletter", "email": "[email protected]", "name": "Ann Lee", "source": "form" }
}

They're signed the same way as delivery events. See Webhooks.

Send an email

POST/v1/email/send

The from domain must be verified under Domains in the dashboard. Without cc or bcc, each to address receives its own copy and can carry its own merge_data. With cc or bcc, one message goes to everyone.

FieldDescription
fromRequired. "[email protected]" or "Acme <[email protected]>".
toRequired. An address, or a list. Items may be objects: { "email", "name", "merge_data" }.
cc, bccOptional. Same formats as to. 50 recipients in total.
subjectRequired unless a template is used. Up to 500 characters.
html, textAt least one, unless a template is used. 500 KB in total.
template_keySend a saved template, e.g. tpl_3f9a1c2b7d4e.
merge_dataValues for {{placeholders}}, shared by all recipients.
reply_toOptional reply address.
attachmentsSee attachments.
tagsUp to 10 key/value pairs, returned in logs and webhooks.
verifytrue runs a quick check first and skips invalid addresses (not charged).
unsubscribe_urlAn https URL added as a one-click List-Unsubscribe header.
curl https://wisesender.com/v1/email/send \
  -H "Authorization: Bearer ws_send_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from": "Acme <[email protected]>",
    "to": [{ "email": "[email protected]", "name": "Jane", "merge_data": { "code": "482913" } }],
    "subject": "Your login code",
    "html": "<p>Hi {{name}}, your code is <b>{{code}}</b></p>",
    "tags": { "type": "otp" }
  }'

Each recipient's status is sent, suppressed (on your do-not-send list), invalid (failed the optional check) or failed. Only sent uses an email credit.

Templates

Create templates in the dashboard and send them with template_key. Placeholders use double braces and support dotted paths:

{{name}}Inserted with HTML escaping. Safe for user-supplied values.
{{order.id}}Reads nested values from merge_data.
{{{html_block}}}Triple braces insert the value as raw HTML. Use only with trusted content.

Unknown placeholders become empty text. A recipient's own merge_data overrides the shared values, and name and email are filled from the recipient automatically.

Attachments

Pass a list of objects with a file name and base64 content. Up to 10 files and 10 MB in total. Executable file types are refused because mailbox providers block them.

"attachments": [
  { "name": "invoice-10482.pdf", "content": "JVBERi0xLjQK..." },
  { "name": "logo.png", "content": "iVBORw0KGgo...", "content_id": "logo" }
]
// reference the inline image in html as <img src="cid:logo">

SMTP relay

Use the relay when your software only speaks SMTP. The same domain checks, limits, logs and webhooks apply, and Bcc headers are removed before delivery.

Hostsmtp.wisesender.com
Port587 with STARTTLS, or 465 with TLS
Usernamewisesender
PasswordA sending key (ws_send_...)

Webhooks

Set an https URL in the dashboard under Transactional API, Webhooks. We POST one JSON event per recipient for sent, delivered, bounced, soft_bounced, complained, delayed, failed, opened and clicked.

{
  "event": "delivered",
  "message_id": "1842",
  "agent": "OTP",
  "from": "[email protected]",
  "to": "[email protected]",
  "recipient_type": "to",
  "subject": "Your login code",
  "tags": { "type": "otp" },
  "timestamp": "2026-11-10T09:14:03.120Z"
}

Check the signature

Every request carries X-WiseSender-Signature: sha256=<hex>, an HMAC-SHA256 of the raw body using your webhook secret. Compare it before trusting the event.

const crypto = require('node:crypto');
function isValid(rawBody, header, secret) {
  const good = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return header?.length === good.length && crypto.timingSafeEqual(Buffer.from(header), Buffer.from(good));
}

Errors

Errors return a non-2xx status and a body like { "error": "Invalid API key" }.

StatusMeaning
400A field is missing or invalid. The message names it.
401The key is missing, wrong or revoked.
402Not enough credits. Buy a pack or wait for the daily refill.
403The sending domain is not verified, the account email isn't confirmed, or sending is paused.
404The endpoint or template does not exist.
413Too many addresses, recipients or bytes in one request.
422A contact was refused by the verify check. The body says why.
429A rate or daily limit was reached. Slow down or try tomorrow.

Limits

Bulk verify1,000 addresses per request; 100,000 per file in the dashboard
Free checks without a key100 per IP address per day
Send API300 requests a minute per account, 50 recipients per request
Contacts API600 requests a minute per account
Daily sending200 emails a day for new accounts. Grows as you buy email credits: 2,000 after 10,000, 10,000 after 50,000, 20,000 after 100,000 and 50,000 after 500,000
Automatic pauseAbove 5% bounces or 0.3% complaints over 7 days, after 100 sends

Need a higher limit? Ask support with your expected volume.