Documentation

API: Send Email

Send one transactional email — an order confirmation, a password reset, a one-time code — from your own application. Write the message yourself with html / text, or send a saved template with merge-tag variables. Every call is recorded, so you can see exactly what happened to it under API Sends.

MethodPOST
Endpoint/api/v1/send
Credits usedDelivery + verification of the recipient (+ tracking, if you turn it on). Nothing is charged when the email is not sent
RequiresA paid plan with API access, and an account approved for campaign access (the same approval Campaigns need)

Your first email

The smallest valid call is a recipient, a subject and a body. With no from_email it sends from your account's own Pingovo address.

curl -X POST https://pingovo.com/api/v1/send \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "[email protected]",
    "subject": "Your order has shipped",
    "html": "<p>Hi Alex, your order #1042 is on its way.</p>",
    "tags": ["orders"]
  }'

Message

FieldTypeRequiredDescription
tostringYesThe recipient email address.
subjectstringWith html / textSubject line. Not needed when you use templateId.
htmlstringOne of html, text or templateIdHTML body. Required for open / click tracking.
textstringOne of html, text or templateIdPlain-text body.
templateIdstringOne of html, text or templateIdThe id of a template in your library — your own or a Pingovo system template.
variablesobjectNoValues for the template’s merge tags, e.g. { "firstName": "Alex" }.
from_emailstringNoSend as any verified Sender on your account. Omit to send from your account’s own Pingovo address. SMTP senders are not supported on this endpoint.
from_namestringNoDisplay name shown next to the address. Characters that could break the header are removed.

Grouping and tracking

FieldTypeDefaultDescription
tagsstring[]—Group sends into streams. Up to 5 tags, 40 characters each: letters, numbers, spaces and _ . : -
campaignstring—Shorthand for one more tag.
track_opensbooleanfalseAdd an open-tracking pixel. Needs an html body.
track_clicksbooleanfalseRewrite links so clicks are counted. Needs an html body.

Safety and pacing

FieldTypeDefaultDescription
verify_before_sendbooleantrueVerify the recipient first and never send to an address that comes back invalid. Only accounts granted a direct-send override can switch this off; for everyone else the flag is ignored and verification still runs.
skip_riskybooleanfalseLeave out risky, catch-all and unverifiable addresses. Needs verify_before_send.
skip_role_basedbooleanfalseLeave out shared inboxes such as info@, sales@ and support@. Needs verify_before_send.
delay_between_emailsnumber (seconds)noneKeep this gap between your account’s paced sends. The call waits for its turn, up to 50 seconds. 0.1 – 3600.
daily_send_limitintegernoneDeliver at most this many emails in any rolling 24 hours. Further calls are refused until the window frees up.

All of these are optional. Boolean fields must be real true / false values — the string "false" is rejected with 400 rather than treated as true, so a typo can never switch on a charge.

Sending a saved template

Build the template in Templates, copy its id, and pass the merge-tag values in variables. Transactional mail never gets an unsubscribe footer appended.

curl -X POST https://pingovo.com/api/v1/send \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "[email protected]",
    "from_email": "[email protected]",
    "from_name": "Acme Support",
    "templateId": "TEMPLATE_ID",
    "variables": { "firstName": "Alex", "orderNumber": "1042" }
  }'

Response

FieldTypeDescription
request_idstringUnique id of this API call. Quote it to support.
send_idstringId of this email’s record. Use it with GET /sends/:id to see delivery and engagement later.
data.tostringThe recipient.
data.subjectstringThe subject that was sent.
data.message_idstringThe Message-ID header of the delivered email.
data.tagsstring[]The tags the send was recorded under.
credits.verification / .send / .trackingnumberThe credits this call spent on each part.
credits_usednumberTotal credits spent by this call.
credits_remainingnumberYour balance afterwards.
tracking.opens / .clicksbooleanWhich kinds of tracking were applied to this email.
delivery.delay_seconds / .waited_msnumberPresent when you used a pacing parameter: how long this call waited for its turn.

Open and click tracking

Tracking is off unless you ask. Turn on track_opens, track_clicks or both and each tracked email costs one flat tracking charge — the same whether you enable one or both. It appears as its own line in your credit history, and it is taken only for an email that was actually delivered.

  • Tracking needs an html body (or an HTML template). Asking for it without one fails with 400 TRACKING_REQUIRES_HTML before anything is charged.
  • If your balance cannot cover delivery and tracking, the call fails with 402 INSUFFICIENT_CREDITS. It is never silently downgraded to an untracked send.
  • Open counts run high, because some mail apps pre-load images. Treat clicks as the more reliable engagement signal. Bots and prefetchers are filtered out of both.
  • Click links always point to the destinations in your email — they cannot be redirected anywhere else.

Delivery controls in practice

This example paces sends 30 seconds apart, caps the account at 500 delivered emails a day, skips risky and role-based addresses, tracks opens and clicks, and handles the refusals. Set your HTTP client's timeout above 50 seconds when you use delay_between_emails — the call is held open while it waits.

curl --max-time 90 -i -X POST https://pingovo.com/api/v1/send \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "[email protected]",
    "subject": "Welcome aboard",
    "html": "<p>Welcome! <a href=\"https://example.com/start\">Get started</a></p>",
    "tags": ["onboarding"],
    "track_opens": true,
    "track_clicks": true,
    "delay_between_emails": 30,
    "daily_send_limit": 500,
    "skip_risky": true,
    "skip_role_based": true
  }'

What happens before a send

  1. The request is validated. Any malformed field fails fast with a 400 and costs nothing.
  2. The recipient is checked against your suppression list — addresses that bounced, unsubscribed or that you suppressed are never mailed (422).
  3. Unless switched off, the recipient is verified. An invalid address is not sent to. The verification is charged even if it leads to a skip.
  4. Your skip rules run (skip_risky, skip_role_based).
  5. The credits for the send are reserved. If delivery then fails, they are returned in full.
  6. The email is delivered, and you get the response above.

Refusals specific to /send

StatuserrorWhat it means
400RECIPIENT_REQUIREDto is missing or not a valid email address.
400SUBJECT_REQUIREDNo subject was given for an html / text send.
400CONTENT_REQUIREDGive html, text, or a templateId.
400INVALID_TAGSMore than 5 tags, a tag over 40 characters, or a disallowed character.
400INVALID_TRACKINGtrack_opens / track_clicks was not true or false.
400TRACKING_REQUIRES_HTMLTracking was requested without an html body.
400INVALID_DELIVERY_SETTINGSdelay_between_emails or daily_send_limit is out of range.
400SKIP_REQUIRES_VERIFICATIONskip_risky / skip_role_based were used with verification switched off.
400INVALID_FIELD_TYPEA field has the wrong type, e.g. variables is not an object.
400INVALID_SENDERfrom_email is not a valid email address.
400SENDER_NOT_FOUNDfrom_email is not a verified Sender on your account. Add and verify it under Senders first.
400SENDER_NOT_SUPPORTEDSMTP senders cannot be used here. Use a domain or free-email sender.
400TEMPLATE_RENDER_ERRORThe template could not be rendered with your variables.
402INSUFFICIENT_CREDITSNot enough credits. The response shows credits_required and credits_available.
403—Your account is not yet approved for campaign access.
403API_KEY_REVOKEDPingovo revoked this key. Contact support.
404TEMPLATE_NOT_FOUNDtemplateId is not a template your account can use.
422RECIPIENT_INVALIDThe address failed verification and was not sent to.
422RECIPIENT_SUPPRESSEDThe address is on your suppression list, bounced before, or unsubscribed.
422RECIPIENT_SKIPPEDOne of your skip rules left the address out. skip_reason says which.
429DAILY_LIMIT_REACHEDYour daily_send_limit is used up. The body has resets_at; Retry-After says how long to wait. Nothing is charged.
429SEND_QUEUE_BUSYToo many sends are already waiting behind delay_between_emails. Retry after Retry-After seconds. Nothing is charged.
502VERIFICATION_ERRORThe pre-send verification could not be completed. Retry.
502SEND_UNAVAILABLESending is temporarily unavailable. Retry shortly.
502SEND_FAILEDThe receiving server refused the email. The body’s message carries its response and classification.

Every one of these — including calls refused before anything was sent — is recorded and visible under Dashboard → API Sends with its error code, so you can see why a call did nothing.

Still have questions?

Our support team is happy to help. Open a ticket from your dashboard or use the contact form.

We use essential cookies to run this site, and optional functional/analytics cookies to improve it. See our Privacy Policy for details.