Send Messages

All message types are sent through a single function: sendMessage(jid, content, options). Plus manual relay via relayMessage for advanced use cases.

sendMessage — Main Function

Parameters

ParameterTypeDescription
jidstringTarget JID. E.g., 628xxx@s.whatsapp.net, xxx@g.us
contentAnyMessageContentMessage content object. Structure varies by message type
optionsMiscMessageGenerationOptionsAdditional configuration: quoted, ephemeral, etc.

Options (3rd Parameter)

OptionTypeDescription
quotedWAMessageMessage to reply to. Pass the full WAMessage object from events
ephemeralExpirationnumber | stringDisappearing message duration: 86400 (24h), 604800 (7d), 7776000 (90d)
timestampDateOverride message timestamp
mediaUploadTimeoutMsnumberTimeout for media upload to WA server
statusJidListstring[]JID list for status broadcast
backgroundColorstringStatus background color
fontnumberStatus font
broadcastbooleanBroadcast message
useCachedGroupMetadatabooleanUse cached group metadata (default true)
messageIdstringOverride message ID with a custom value

Return Value

sendMessage returns Promise<WAMessage>. The returned object has a .key (MessageKey) that can be used for edit, delete, reply:

const sent = await sock.sendMessage(jid, { text: 'Hello' })
console.log(sent.key.id)  // message ID
console.log(sent.key)     // { id, remoteJid, fromMe }

When emitOwnEvents: true (default), sent messages also appear in the messages.upsert event — so you can handle your own messages through event listeners.


Text

Basic text messages. Use text for content, mentions to mention people, mentionAll to mention everyone.

Field Content

FieldTypeRequiredDescription
textstringYesMessage text content
mentionsstring[]NoJIDs to mention
mentionAllbooleanNoMention all group participants
contextInfoproto.IContextInfoNoCustom context info
linkPreviewWAUrlInfo | nullNoOverride link preview. Null = disable
await sock.sendMessage('6281234567890@s.whatsapp.net', { text: 'Hello from bot!' })

// With mention
await sock.sendMessage('123@g.us', { text: 'Hello @6281234567890!', mentions: ['6281234567890@s.whatsapp.net'] })

// Mention all
await sock.sendMessage('123@g.us', { text: 'Hello everyone!', mentionAll: true })

// Reply / quoted
await sock.sendMessage(jid, { text: 'Reply' }, { quoted: originalMsg })

// Disappearing message
await sock.sendMessage(jid, { text: 'Disappears in 24h' }, { ephemeralExpiration: 86400 })

// View once
await sock.sendMessage(jid, { text: 'View once text', viewOnce: true })

// Edit sent message
await sock.sendMessage(jid, { text: 'Edit me', edit: messageKey })

originalMsg is the full WAMessage object from the messages.upsert event. Store it in memory or database, then pass it to quoted.

Image

image accepts Buffer, URL ({ url: string }), or Readable stream.

Field Content

FieldTypeRequiredDescription
imageWAMediaUploadYesBuffer / { url: string } / Readable stream
captionstringNoImage caption
mentionsstring[]NoMentions in caption
jpegThumbnailstring / BufferNoCustom thumbnail (Base64 string or Buffer)
width / heightnumberNoImage dimensions
contextInfoproto.IContextInfoNoContext info
viewOncebooleanNoView once
albumParentKeyWAMessageKeyNoAlbum parent key (if part of an album)
await sock.sendMessage(jid, { image: { url: 'https://example.com/photo.jpg' }, caption: 'Nice' })
await sock.sendMessage(jid, { image: fs.readFileSync('./photo.jpg'), caption: 'Local' })
await sock.sendMessage(jid, { image: { url: './photo.jpg' }, viewOnce: true })

Video

ptv: true = video note (circle). gifPlayback: true = animated GIF.

Field Content

FieldDescription
videoBuffer / URL / stream
captionCaption
gifPlaybacktrue = send as GIF
ptvtrue = video note (circle)
jpegThumbnailCustom thumbnail
width / heightDimensions
mentionsMentions in caption
await sock.sendMessage(jid, { video: { url: './video.mp4' }, caption: 'Video' })
await sock.sendMessage(jid, { video: { url: './video.mp4' }, ptv: true })
await sock.sendMessage(jid, { video: { url: './video.gif' }, gifPlayback: true })

Audio / Voice Note

ptt: true = voice note. seconds can specify duration (optional).

Field Content

FieldDescription
audioBuffer / URL / stream
ptttrue = voice note
secondsAudio duration in seconds
mimetypeE.g., audio/ogg; codecs=opus, audio/mp4, audio/aac
// Voice note
await sock.sendMessage(jid, { audio: { url: './voice.ogg' }, mimetype: 'audio/ogg; codecs=opus', ptt: true })
// Regular audio
await sock.sendMessage(jid, { audio: { url: './song.mp3' }, mimetype: 'audio/mp4' })

Document

Send any file type. mimetype must be correct.

Field Content

FieldDescription
documentBuffer / URL / stream
fileNameDisplay file name
mimetypeRequired. E.g., application/pdf
captionCaption
contextInfoContext info
await sock.sendMessage(jid, {
  document: { url: './file.pdf' },
  fileName: 'document.pdf',
  mimetype: 'application/pdf',
  caption: 'Here is the PDF',
})

Sticker

Send a sticker. isAnimated for animated stickers (webp with multiple frames).

Field Content

FieldDescription
stickerBuffer / URL (webp)
isAnimatedtrue for animated sticker
width / heightOptional dimensions
await sock.sendMessage(jid, { sticker: fs.readFileSync('./sticker.webp') })
await sock.sendMessage(jid, { sticker: { url: './animated.webp' }, isAnimated: true })

Poll

Create a poll with options. selectableCount: 1 = single select, >1 = multi-select. values: array of choices.

PollMessageOptions

FieldTypeDescription
namestringPoll question
valuesstring[]Poll options
selectableCountnumber1 = single select, >1 = multi-select
messageSecretUint8Array32-byte secret for vote encryption
toAnnouncementGroupbooleanFor announcement groups
await sock.sendMessage(jid, {
  poll: { name: 'Favorite framework?', values: ['Next.js', 'React', 'Vue'], selectableCount: 1 }
})
await sock.sendMessage(jid, {
  poll: { name: 'Skills?', values: ['TS', 'Python', 'Go'], selectableCount: 2 }
})

Location

Send a map location. Uses proto.Message.ILocationMessage.

Field Content

FieldDescription
degreesLatitudeLatitude (-6.2)
degreesLongitudeLongitude (106.8)
nameLocation name (optional)
addressAddress (optional)
commentComment (optional)
await sock.sendMessage(jid, {
  location: { degreesLatitude: -6.2, degreesLongitude: 106.8, name: 'Jakarta' }
})

Contact (vCard)

Send a contact as vCard.

Field Content

FieldDescription
contacts.displayNameDisplay name
contacts.contactsArray of { vcard: string }
const vcard = [
  'BEGIN:VCARD', 'VERSION:3.0', 'FN:Contact Name',
  'TEL;waid=628xxx:+6281234567890', 'END:VCARD',
].join('\n')

await sock.sendMessage(jid, {
  contacts: { displayName: 'Contact Name', contacts: [{ vcard }] }
})

Reaction

Add or remove emoji reactions on existing messages. react.text = emoji, empty string removes the reaction.

Must use sendMessage — not a separate function.

// Add reaction
await sock.sendMessage(jid, { react: { text: '🔥', key: messageKey } })
// Remove reaction
await sock.sendMessage(jid, { react: { text: '', key: messageKey } })

Edit Message

Edit the text of an already-sent message. Pass the target MessageKey via edit. New text via text or caption.

Can only edit your own messages (fromMe: true). Time limit: not fixed, but usually within 24 hours.

// Edit text
await sock.sendMessage(jid, { text: 'Updated text', edit: messageKey })
// Edit media caption
await sock.sendMessage(jid, { image: { url: 'photo.jpg' }, caption: 'Updated caption', edit: messageKey })

messageKey comes from sent.key if sent by yourself, or from msg.key in the messages.upsert event.

Delete Message

Delete a message. delete takes a MessageKey.

Important Notes

// Delete own message
await sock.sendMessage(jid, { delete: messageKey })
// Admin delete others' message in group
await sock.sendMessage(jid, {
  delete: { id: msgKey.id, remoteJid: jid, fromMe: false, participant: msgKey.participant }
})

Forward

Forward a message from one chat to another. forward = the original WAMessage object to forward. force: true removes the "Forwarded" label.

// Normal forward (with "Forwarded" label)
await sock.sendMessage(targetJid, { forward: originalMessage })
// Force forward (without label)
await sock.sendMessage(targetJid, { forward: originalMessage, force: true })

View Once

Add viewOnce: true to any message type. Recipient can only view once.

await sock.sendMessage(jid, { image: { url: './secret.jpg' }, viewOnce: true })
await sock.sendMessage(jid, { video: { url: './video.mp4' }, viewOnce: true })
await sock.sendMessage(jid, { text: 'View once text', viewOnce: true })
await sock.sendMessage(jid, { audio: { url: './voice.ogg' }, viewOnce: true, ptt: true })

Disappearing Messages

Two modes: set disappearing for the entire chat, or send a single disappearing message.

Mode 1: Set for entire chat (groups only)

Use disappearingMessagesInChat. Type boolean | number:

Only works in groups. For personal chats use updateDefaultDisappearingMode.

// Set 24h in group
await sock.sendMessage(jid, { disappearingMessagesInChat: 86400 })
// Disable
await sock.sendMessage(jid, { disappearingMessagesInChat: 0 })

Mode 2: Single disappearing message

Use ephemeralExpiration option (3rd parameter):

await sock.sendMessage(jid, { text: 'Secret' }, { ephemeralExpiration: 86400 })
await sock.sendMessage(jid, { image: { url: './photo.jpg' } }, { ephemeralExpiration: 604800 })

Album (Multi Media)

Send multiple media items grouped as one album. The first image/video becomes the cover.

How It Works

  1. Send the first media with album: { expectedImageCount, expectedVideoCount } — this becomes the parent album
  2. Save albumParentKey from the first send result (= sent.key)
  3. Send subsequent media with albumParentKey on each media
const album = await sock.sendMessage(jid, {
  image: { url: './1.jpg' },
  caption: 'Album',
  album: { expectedImageCount: 3 },
})
await sock.sendMessage(jid, { image: { url: './2.jpg' }, albumParentKey: album.key })
await sock.sendMessage(jid, { image: { url: './3.jpg' }, albumParentKey: album.key })

Event (Calendar)

Create an event invitation with date, description, location, and auto-generated RSVP buttons from WA.

EventMessageOptions

FieldTypeDescription
namestringEvent name
descriptionstringEvent description
startDateDateStart date/time
endDateDateEnd date/time (optional)
locationWALocationMessageLocation (lat, lng, name)
call'audio' | 'video'Add call link button
isCancelledbooleanMark event as cancelled
isScheduleCallbooleanSchedule a call
extraGuestsAllowedbooleanAllow extra guests
await sock.sendMessage(jid, {
  event: {
    name: 'Community Meetup',
    description: 'Tech discussion',
    startDate: new Date('2025-07-01T10:00:00+07:00'),
    endDate: new Date('2025-07-01T12:00:00+07:00'),
    location: { degreesLatitude: -6.2, degreesLongitude: 106.8, name: 'Jakarta' },
    call: 'video',
  }
})

Pin Message

Pin a message in chat. Requires the target message's MessageKey.

Field Content

FieldTypeDescription
pinWAMessageKeyMessage key of the message to pin
typeproto.PinInChat.Type1 = PINNED, 2 = UNPINNED
time86400 | 604800 | 2592000Pin duration: 24h / 7d / 30d
await sock.sendMessage(jid, { pin: messageKey, time: 604800 })   // Pin 7 days
await sock.sendMessage(jid, { pin: messageKey, type: 2 })         // Unpin

HD Media

Send images/videos in HD quality. Just add isHd: true.

// HD Image
await sock.sendMessage(jid, {
  image: { url: './photo.jpg' },
  caption: 'HD photo!',
  isHd: true,
})

// HD Video
await sock.sendMessage(jid, {
  video: { url: './video.mp4' },
  caption: 'HD video!',
  isHd: true,
})

isHd tells WhatsApp to show an "HD" badge on the message. The uploaded file stays at original quality. Works on WhatsApp clients with HD media support (Android/Web). iOS may display as a regular image.

Keep In Chat

Prevent a message from disappearing. Useful for saving important messages in chats with disappearing mode enabled.

// Keep message (prevent deletion)
await sock.sendMessage(jid, {
  keepInChat: messageKey,
  type: 1, // KEEP_FOR_ALL
})

// Undo keep (restore disappearing)
await sock.sendMessage(jid, {
  keepInChat: messageKey,
  type: 2, // UNDO_KEEP_FOR_ALL
})

Product

Send a product message from a business catalog.

Field Content

FieldDescription
product.productImageProduct image (Buffer/URL)
product.titleProduct title
product.descriptionProduct description
product.currencyCodeCurrency code (IDR)
product.priceAmountPrice in cents (10000 = $100)
product.retailerIdProduct SKU
businessOwnerJidBusiness owner JID
bodyAdditional message
footerFooter
await sock.sendMessage(jid, {
  product: {
    productImage: { url: './product.jpg' },
    title: 'Premium T-Shirt',
    description: 'Comfortable cotton',
    currencyCode: 'IDR',
    priceAmount: 5000000,
    retailerId: 'SKU001',
  },
  businessOwnerJid: '628xxx@s.whatsapp.net',
  body: 'Check out this product!',
  footer: 'Our Store',
})

Group Invite

Send a group invitation.

await sock.sendMessage(jid, {
  groupInvite: {
    inviteCode: 'abc123',
    inviteExpiration: 86400,
    text: 'Join our group!',
    jid: '123456@g.us',
    subject: 'Awesome Group',
  }
})

Share Phone Number

Share your own phone number in a chat.

await sock.sendMessage(jid, { sharePhoneNumber: true })

Limit Sharing

Limit message forwarding/sharing.

await sock.sendMessage(jid, { limitSharing: true })

Status / Story Broadcast

Send a status/story update — the same kind that appears at the top of WhatsApp chats. Send to status@broadcast and optionally control who can see it via statusJidList.

Text Status

await sock.sendMessage('status@broadcast', {
  text: 'Hello world! 🌍',
}, {
  statusJidList: [
    '628xxx@s.whatsapp.net',
    '628yyy@s.whatsapp.net',
  ],
  backgroundColor: '#4a9eff',
  font: 1,
})

Image Status

await sock.sendMessage('status@broadcast', {
  image: { url: './status.jpg' },
  caption: 'Trying new features!',
}, {
  statusJidList: ['628xxx@s.whatsapp.net'],
})

Optional Parameters

ParamTypeDescription
statusJidListstring[]JIDs allowed to view. If omitted, all contacts can see based on privacy settings
backgroundColorstringBackground color (hex). E.g., '#4a9eff'
fontnumberFont type. 0-3 (WhatsApp supported fonts)

status@broadcast is different from regular broadcasts. statusJidList is optional — if omitted, the privacy setting determines viewers. If provided, only those JIDs can see the status.


relayMessage — Manual Send

For more control (e.g., adding custom nodes, setting participant manually), use relayMessage directly:

relayMessage Parameters

ParamTypeDescription
jidstringTarget
messageproto.IMessagePre-generated protobuf message
optionsMessageRelayOptionsAdditional relay config
import { generateWAMessage, proto } from '@mataram/wa'
const msg = await generateWAMessage(jid, { text: 'Manual' }, { userJid: sock.user?.id })
await sock.relayMessage(jid, msg.message!, { messageId: msg.key.id })
← Back Next: Buttons →