Messaging
Sending contact-card messages
Send structured contact details as a WhatsApp contact card.
What this guide covers
- Provide the contact's display name and supported contact fields.
- Validate phone and email values before submitting the message.
- Avoid including contact data that the recipient does not need.
Send contact cards
A request can contain from one through ten structured contacts. Every contact requires name.formattedName; phone, email, and organization fields are optional.
import { WhatsAppClient } from "@yanola/whatsapp"
const apiKey = process.env.YANOLA_API_KEY
if (!apiKey) {
throw new Error("Missing YANOLA_API_KEY")
}
const yanola = new WhatsAppClient({ apiKey })
const accepted = await yanola.messages.sendContactCard({
from: "+15557654321",
to: "+15551234567",
contacts: [
{
name: {
formattedName: "Amina Rahman",
firstName: "Amina",
lastName: "Rahman",
},
phones: [
{
phone: "+971501234567",
type: "WORK",
},
],
emails: [
{
email: "amina@example.com",
type: "WORK",
},
],
org: {
company: "Example Logistics",
title: "Dispatch manager",
},
},
],
})
console.log(accepted.id)Contact structure
| Field | Contract |
|---|---|
contacts | Required array containing 1 to 10 contacts. |
name.formattedName | Required non-empty display name, up to 512 characters. |
name.firstName / lastName | Optional structured name parts. |
phones | Optional entries with a non-empty phone and optional type. |
emails | Optional entries with a valid email and optional type. |
org | Optional company, department, and title strings. |
Validate application data first
Yanola rejects unknown object fields, invalid emails, empty required values, and more than ten contacts with 400 invalid_message_request. Use the returned issues to locate the failing nested field.
The recipient and Sender must be E.164. Contact-card phone entries must be non-empty; normalize them in your application if recipients depend on consistent international formatting.
Customer Service Window
In Live Mode, a text, media, location, or contact-card send is allowed only when Yanola has evidence that the recipient sent an inbound message to the same Sender during the previous 24 hours. Activity with another Sender does not open this window.
When no active window is known, Yanola returns 422 service_window_required before queueing the message or making a provider call. The error is not retryable: repeating the same non-template request cannot create a window.
- Send an approved template that is appropriate for the business intent.
- After the customer replies, use non-template messages during the new 24-hour window.
- Do not catch the error and silently substitute unrelated template content.
{
"error": {
"type": "business_state_error",
"code": "service_window_required",
"message": "A text, media, location, or contact-card message requires an active Customer Service Window. Send an approved template instead.",
"requestId": "req_...",
"retryable": false,
"details": {
"from": "+15557654321",
"to": "+15551234567"
}
}
}Test Mode still validates the recipient and connected Sender but does not require stored inbound-window evidence. Template sends are also exempt because an approved template is the compliant way to start a conversation.
Protect contact data
- Share only fields the WhatsApp recipient is authorized to receive.
- Do not use a contact card as a general-purpose customer-data export.
- Review application logs and queues so they do not retain unnecessary contact details.
- Use a template first when initiating a Live Mode conversation outside the Customer Service Window.
Asynchronous acceptance
Every send method returns a queued response after Yanola accepts the operation. A 202 response is not proof of provider delivery.
Live message IDs start with msg_; Test Mode IDs start with msg_test_. Keep the returned ID and request ID so you can correlate message retrieval, webhook events, logs, failures, and support requests.
{
"id": "msg_...",
"environment": "live",
"status": "queued",
"from": "+15557654321",
"to": "+15551234567",
"requestId": "req_...",
"createdAt": "2026-07-29T10:00:00.000Z"
}Use client.messages.retrieve(accepted.id), message logs, or signed webhook events to observe sent, delivered, read, failed, canceled, or reconciliation-needed states.
Safe retries and idempotency
The SDK generates a unique Idempotency-Key for every send call and preserves it across automatic transport retries. If your job system owns retry identity, pass a stable idempotencyKey as the second argument.
The same key and request body return the original accepted response. Reusing the key with a different body returns 409 idempotency_key_reused. Yanola retains send idempotency records for at least 24 hours.
const accepted = await yanola.messages.sendContactCard(
{
from: "+15557654321",
to: "+15551234567",
contacts: [
{
name: { formattedName: "Amina Rahman" },
phones: [{ phone: "+971501234567", type: "WORK" }],
},
],
},
{
idempotencyKey: "order-123-dispatch-contact",
}
)Persist the key with the business operation that caused the send. Do not reuse one key for multiple recipients, message types, or changed content.