Browse documentation

Messaging

Sending contact-card messages

Send structured contact details as a WhatsApp contact card.

DevelopersAdvancedAvailable

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.

Contact-card send
ts
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

Contact-card fields
FieldContract
contactsRequired array containing 1 to 10 contacts.
name.formattedNameRequired non-empty display name, up to 512 characters.
name.firstName / lastNameOptional structured name parts.
phonesOptional entries with a non-empty phone and optional type.
emailsOptional entries with a valid email and optional type.
orgOptional 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.

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.

  1. Send an approved template that is appropriate for the business intent.
  2. After the customer replies, use non-template messages during the new 24-hour window.
  3. Do not catch the error and silently substitute unrelated template content.
Customer Service Window error
json
{
  "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"
    }
  }
}

Protect contact data

  1. Share only fields the WhatsApp recipient is authorized to receive.
  2. Do not use a contact card as a general-purpose customer-data export.
  3. Review application logs and queues so they do not retain unnecessary contact details.
  4. 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.

Accepted message
json
{
  "id": "msg_...",
  "environment": "live",
  "status": "queued",
  "from": "+15557654321",
  "to": "+15551234567",
  "requestId": "req_...",
  "createdAt": "2026-07-29T10:00:00.000Z"
}

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.

Application-owned idempotency
ts
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",
  }
)