whatsapp-cloud 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +11 -0
  3. package/agent_docs/DESIGN.md +707 -0
  4. package/agent_docs/MESSAGES_NAMESPACE_ANALYSIS.md +368 -0
  5. package/agent_docs/NAMING_DECISION.md +78 -0
  6. package/agent_docs/STRUCTURE.md +711 -0
  7. package/agent_docs/messages-namespace-design.md +357 -0
  8. package/package.json +5 -2
  9. package/src/client/HttpClient.ts +122 -0
  10. package/src/client/WhatsAppClient.ts +55 -0
  11. package/src/client/index.ts +2 -0
  12. package/src/errors.ts +58 -0
  13. package/src/index.ts +16 -1
  14. package/src/schemas/accounts/index.ts +1 -0
  15. package/src/schemas/accounts/phone-number.ts +20 -0
  16. package/src/schemas/business/account.ts +43 -0
  17. package/src/schemas/business/index.ts +2 -0
  18. package/src/schemas/client.ts +50 -0
  19. package/src/schemas/debug.ts +25 -0
  20. package/src/schemas/index.ts +5 -0
  21. package/src/schemas/messages/index.ts +2 -0
  22. package/src/schemas/messages/request.ts +82 -0
  23. package/src/schemas/messages/response.ts +19 -0
  24. package/src/services/accounts/AccountsClient.ts +42 -0
  25. package/src/services/accounts/AccountsService.ts +47 -0
  26. package/src/services/accounts/index.ts +2 -0
  27. package/src/services/accounts/methods/list-phone-numbers.ts +16 -0
  28. package/src/services/business/BusinessClient.ts +42 -0
  29. package/src/services/business/BusinessService.ts +47 -0
  30. package/src/services/business/index.ts +3 -0
  31. package/src/services/business/methods/list-accounts.ts +18 -0
  32. package/src/services/index.ts +2 -0
  33. package/src/services/messages/MessagesClient.ts +38 -0
  34. package/src/services/messages/MessagesService.ts +77 -0
  35. package/src/services/messages/index.ts +8 -0
  36. package/src/services/messages/methods/send-image.ts +33 -0
  37. package/src/services/messages/methods/send-location.ts +32 -0
  38. package/src/services/messages/methods/send-reaction.ts +33 -0
  39. package/src/services/messages/methods/send-text.ts +32 -0
  40. package/src/services/messages/utils/build-message-payload.ts +32 -0
  41. package/src/types/accounts/index.ts +1 -0
  42. package/src/types/accounts/phone-number.ts +9 -0
  43. package/src/types/business/account.ts +10 -0
  44. package/src/types/business/index.ts +2 -0
  45. package/src/types/client.ts +8 -0
  46. package/src/types/debug.ts +8 -0
  47. package/src/types/index.ts +5 -0
  48. package/src/types/messages/index.ts +2 -0
  49. package/src/types/messages/request.ts +27 -0
  50. package/src/types/messages/response.ts +7 -0
  51. package/src/utils/zod-error.ts +28 -0
  52. package/tsconfig.json +4 -1
@@ -0,0 +1,2 @@
1
+ export * from "./account";
2
+
@@ -0,0 +1,50 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Helper message for access token errors
5
+ */
6
+ const ACCESS_TOKEN_HELP_MESSAGE =
7
+ "Get your access token from Meta for Developers: https://developers.facebook.com/docs/whatsapp/cloud-api/get-started";
8
+
9
+ /**
10
+ * Schema for access token with validation and helpful error messages
11
+ */
12
+ const accessTokenSchema = z
13
+ .string({
14
+ message: `accessToken is required. ${ACCESS_TOKEN_HELP_MESSAGE}`,
15
+ })
16
+ .min(1, {
17
+ message: `accessToken cannot be empty. ${ACCESS_TOKEN_HELP_MESSAGE}`,
18
+ })
19
+ .trim()
20
+ .refine((val) => val.length > 0, {
21
+ message: `accessToken cannot be whitespace only. ${ACCESS_TOKEN_HELP_MESSAGE}`,
22
+ });
23
+
24
+ /**
25
+ * Client configuration schema
26
+ */
27
+ export const clientConfigSchema = z.object({
28
+ accessToken: accessTokenSchema,
29
+ phoneNumberId: z
30
+ .string()
31
+ .optional()
32
+ .refine((val) => val === undefined || val.trim().length > 0, {
33
+ message: "phoneNumberId cannot be empty or whitespace only",
34
+ }),
35
+ businessAccountId: z
36
+ .string()
37
+ .optional()
38
+ .refine((val) => val === undefined || val.trim().length > 0, {
39
+ message: "businessAccountId cannot be empty or whitespace only",
40
+ }),
41
+ businessId: z
42
+ .string()
43
+ .optional()
44
+ .refine((val) => val === undefined || val.trim().length > 0, {
45
+ message: "businessId cannot be empty or whitespace only",
46
+ }),
47
+ apiVersion: z.string().default("v18.0"),
48
+ baseURL: z.string().url().default("https://graph.facebook.com"),
49
+ timeout: z.number().positive().optional(),
50
+ });
@@ -0,0 +1,25 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Schema for debug token response
5
+ * Matches Graph API debug_token endpoint response structure
6
+ */
7
+ export const debugTokenResponseSchema = z.object({
8
+ data: z.object({
9
+ app_id: z.string().optional(),
10
+ type: z.string().optional(),
11
+ application: z.string().optional(),
12
+ data_access_expires_at: z.number().optional(),
13
+ expires_at: z.number().optional(),
14
+ is_valid: z.boolean().optional(),
15
+ issued_at: z.number().optional(),
16
+ metadata: z
17
+ .object({
18
+ auth_type: z.string().optional(),
19
+ sso: z.string().optional(),
20
+ })
21
+ .optional(),
22
+ scopes: z.array(z.string()).optional(),
23
+ user_id: z.string().optional(),
24
+ }),
25
+ });
@@ -0,0 +1,5 @@
1
+ export * from "./client";
2
+ export * from "./messages/index";
3
+ export * from "./accounts/index";
4
+ export * from "./business/index";
5
+ export * from "./debug";
@@ -0,0 +1,2 @@
1
+ export * from "./request";
2
+ export * from "./response";
@@ -0,0 +1,82 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Base schema for all message requests
5
+ * phoneNumberId is handled at the client level, not in the request object
6
+ */
7
+ const baseMessageRequestSchema = z.object({
8
+ to: z.string().regex(/^\+[1-9]\d{1,14}$/, "Invalid phone number format"),
9
+ });
10
+
11
+ /**
12
+ * Schema for image object in image messages
13
+ * Matches WhatsApp API structure
14
+ */
15
+ const imageSchema = z
16
+ .object({
17
+ id: z.string().optional(),
18
+ link: z.string().url().optional(),
19
+ caption: z.string().max(1024).optional(),
20
+ })
21
+ .refine((data) => data.link || data.id, "Either link or id must be provided");
22
+
23
+ /**
24
+ * Schema for sending an image message
25
+ * Matches the structure expected by WhatsApp API (minus messaging_product, recipient_type, type, and phoneNumberId)
26
+ */
27
+ export const sendImageRequestSchema = baseMessageRequestSchema.extend({
28
+ image: imageSchema,
29
+ });
30
+
31
+ /**
32
+ * Schema for text object in text messages
33
+ * Matches WhatsApp API structure
34
+ */
35
+ const textSchema = z.object({
36
+ body: z.string().min(1).max(4096),
37
+ preview_url: z.boolean().optional(),
38
+ });
39
+
40
+ /**
41
+ * Schema for sending a text message
42
+ * Matches the structure expected by WhatsApp API (minus messaging_product, recipient_type, type, and phoneNumberId)
43
+ */
44
+ export const sendTextRequestSchema = baseMessageRequestSchema.extend({
45
+ text: textSchema,
46
+ });
47
+
48
+ /**
49
+ * Schema for location object in location messages
50
+ * Matches WhatsApp API structure
51
+ */
52
+ const locationSchema = z.object({
53
+ longitude: z.number().min(-180).max(180),
54
+ latitude: z.number().min(-90).max(90),
55
+ name: z.string().optional(),
56
+ address: z.string().optional(),
57
+ });
58
+
59
+ /**
60
+ * Schema for sending a location message
61
+ * Matches the structure expected by WhatsApp API (minus messaging_product, recipient_type, type, and phoneNumberId)
62
+ */
63
+ export const sendLocationRequestSchema = baseMessageRequestSchema.extend({
64
+ location: locationSchema,
65
+ });
66
+
67
+ /**
68
+ * Schema for reaction object in reaction messages
69
+ * Matches WhatsApp API structure
70
+ */
71
+ const reactionSchema = z.object({
72
+ message_id: z.string().min(1),
73
+ emoji: z.string().min(1).max(1), // Single emoji character
74
+ });
75
+
76
+ /**
77
+ * Schema for sending a reaction message
78
+ * Matches the structure expected by WhatsApp API (minus messaging_product, recipient_type, type, and phoneNumberId)
79
+ */
80
+ export const sendReactionRequestSchema = baseMessageRequestSchema.extend({
81
+ reaction: reactionSchema,
82
+ });
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * Schema for message response
5
+ */
6
+ export const messageResponseSchema = z.object({
7
+ messaging_product: z.literal("whatsapp"),
8
+ contacts: z.array(
9
+ z.object({
10
+ input: z.string(),
11
+ wa_id: z.string(),
12
+ })
13
+ ),
14
+ messages: z.array(
15
+ z.object({
16
+ id: z.string(),
17
+ })
18
+ ),
19
+ });
@@ -0,0 +1,42 @@
1
+ import type { HttpClient } from "../../client/HttpClient";
2
+
3
+ /**
4
+ * Accounts client - wraps HttpClient with WABA ID (WhatsApp Business Account ID) as base endpoint
5
+ *
6
+ * This client automatically prepends `/${wabaId}` to all request paths,
7
+ * so methods can use relative paths like `/phone_numbers` instead of `/${wabaId}/phone_numbers`.
8
+ *
9
+ * Note: The wabaId is the WhatsApp Business Account ID (not the Business Portfolio ID).
10
+ * This is used in endpoints like GET /<WABA_ID>/phone_numbers.
11
+ *
12
+ * This treats wabaId as a "client" for the accounts namespace - different
13
+ * wabaIds represent different WhatsApp Business Account endpoints.
14
+ */
15
+ export class AccountsClient {
16
+ constructor(
17
+ private readonly httpClient: HttpClient,
18
+ private readonly wabaId: string
19
+ ) {}
20
+
21
+ /**
22
+ * Make a GET request with WABA ID prefix
23
+ */
24
+ async get<T>(path: string): Promise<T> {
25
+ return this.httpClient.get<T>(`/${this.wabaId}${path}`);
26
+ }
27
+
28
+ /**
29
+ * Make a POST request with WABA ID prefix
30
+ */
31
+ async post<T>(path: string, body: unknown): Promise<T> {
32
+ return this.httpClient.post<T>(`/${this.wabaId}${path}`, body);
33
+ }
34
+
35
+ /**
36
+ * Make a PATCH request with WABA ID prefix
37
+ */
38
+ async patch<T>(path: string, body: unknown): Promise<T> {
39
+ return this.httpClient.patch<T>(`/${this.wabaId}${path}`, body);
40
+ }
41
+ }
42
+
@@ -0,0 +1,47 @@
1
+ import type { HttpClient } from "../../client/HttpClient";
2
+ import { AccountsClient } from "./AccountsClient";
3
+ import { listPhoneNumbers } from "./methods/list-phone-numbers";
4
+ import { WhatsAppValidationError } from "../../errors";
5
+ import type { PhoneNumberListResponse } from "../../types/accounts/phone-number";
6
+
7
+ /**
8
+ * Accounts service for managing WhatsApp Business Accounts
9
+ *
10
+ * The service validates that businessAccountId (WABA ID - WhatsApp Business Account ID) is set
11
+ * at the client level and creates an AccountsClient instance.
12
+ *
13
+ * Note: businessAccountId in the client config represents the WABA ID, not the Business Portfolio ID.
14
+ * The WABA ID is used in endpoints like GET /<WABA_ID>/phone_numbers.
15
+ *
16
+ * AccountsClient treats wabaId as a "client" for the accounts namespace - different
17
+ * wabaIds represent different WhatsApp Business Account endpoints.
18
+ */
19
+ export class AccountsService {
20
+ private readonly accountsClient: AccountsClient;
21
+
22
+ constructor(httpClient: HttpClient) {
23
+ // Validate that businessAccountId (WABA ID) is set at client level
24
+ // This is the WhatsApp Business Account ID, not the Business Portfolio ID
25
+ if (!httpClient.businessAccountId) {
26
+ throw new WhatsAppValidationError(
27
+ "businessAccountId (WABA ID - WhatsApp Business Account ID) is required for AccountsService. Provide it in WhatsAppClient config.",
28
+ "businessAccountId"
29
+ );
30
+ }
31
+
32
+ // Create accounts client with WABA ID baked in
33
+ this.accountsClient = new AccountsClient(
34
+ httpClient,
35
+ httpClient.businessAccountId
36
+ );
37
+ }
38
+
39
+ /**
40
+ * List phone numbers for a WhatsApp Business Account
41
+ *
42
+ * @returns List of phone numbers associated with the WABA
43
+ */
44
+ async listPhoneNumbers(): Promise<PhoneNumberListResponse> {
45
+ return listPhoneNumbers(this.accountsClient);
46
+ }
47
+ }
@@ -0,0 +1,2 @@
1
+ export { AccountsService } from "./AccountsService";
2
+ export type { PhoneNumberListResponse } from "../../types/accounts/phone-number";
@@ -0,0 +1,16 @@
1
+ import type { AccountsClient } from "../AccountsClient";
2
+ import type { PhoneNumberListResponse } from "../../../types/accounts/phone-number";
3
+
4
+ /**
5
+ * List phone numbers for a WhatsApp Business Account
6
+ *
7
+ * @param accountsClient - Accounts client with WABA ID baked in
8
+ * @returns List of phone numbers associated with the WABA
9
+ */
10
+ export async function listPhoneNumbers(
11
+ accountsClient: AccountsClient
12
+ ): Promise<PhoneNumberListResponse> {
13
+ // Make API request - accountsClient handles the WABA ID prefix automatically
14
+ return accountsClient.get<PhoneNumberListResponse>("/phone_numbers");
15
+ }
16
+
@@ -0,0 +1,42 @@
1
+ import type { HttpClient } from "../../client/HttpClient";
2
+
3
+ /**
4
+ * Business client - wraps HttpClient with Business Portfolio ID as base endpoint
5
+ *
6
+ * This client automatically prepends `/${businessId}` to all request paths,
7
+ * so methods can use relative paths like `/whatsapp_business_accounts` instead of `/${businessId}/whatsapp_business_accounts`.
8
+ *
9
+ * Note: The businessId is the Business Portfolio ID (not the WABA ID).
10
+ * This is used in endpoints like GET /<Business-ID>/whatsapp_business_accounts.
11
+ *
12
+ * This treats businessId as a "client" for the business namespace - different
13
+ * businessIds represent different Business Portfolio endpoints.
14
+ */
15
+ export class BusinessClient {
16
+ constructor(
17
+ private readonly httpClient: HttpClient,
18
+ private readonly businessId: string
19
+ ) {}
20
+
21
+ /**
22
+ * Make a GET request with Business Portfolio ID prefix
23
+ */
24
+ async get<T>(path: string): Promise<T> {
25
+ return this.httpClient.get<T>(`/${this.businessId}${path}`);
26
+ }
27
+
28
+ /**
29
+ * Make a POST request with Business Portfolio ID prefix
30
+ */
31
+ async post<T>(path: string, body: unknown): Promise<T> {
32
+ return this.httpClient.post<T>(`/${this.businessId}${path}`, body);
33
+ }
34
+
35
+ /**
36
+ * Make a PATCH request with Business Portfolio ID prefix
37
+ */
38
+ async patch<T>(path: string, body: unknown): Promise<T> {
39
+ return this.httpClient.patch<T>(`/${this.businessId}${path}`, body);
40
+ }
41
+ }
42
+
@@ -0,0 +1,47 @@
1
+ import type { HttpClient } from "../../client/HttpClient";
2
+ import { BusinessClient } from "./BusinessClient";
3
+ import { listAccounts } from "./methods/list-accounts";
4
+ import { WhatsAppValidationError } from "../../errors";
5
+ import type { BusinessAccountsListResponse } from "../../types/business/account";
6
+
7
+ /**
8
+ * Business service for managing Business Portfolios
9
+ *
10
+ * The service validates that businessId (Business Portfolio ID) is set at the client level
11
+ * and creates a BusinessClient instance.
12
+ *
13
+ * Note: businessId in the client config represents the Business Portfolio ID.
14
+ * The Business Portfolio ID is used in endpoints like GET /<Business-ID>/whatsapp_business_accounts.
15
+ *
16
+ * BusinessClient treats businessId as a "client" for the business namespace - different
17
+ * businessIds represent different Business Portfolio endpoints.
18
+ */
19
+ export class BusinessService {
20
+ private readonly businessClient: BusinessClient;
21
+
22
+ constructor(httpClient: HttpClient) {
23
+ // Validate that businessId (Business Portfolio ID) is set at client level
24
+ if (!httpClient.businessId) {
25
+ throw new WhatsAppValidationError(
26
+ "businessId (Business Portfolio ID) is required for BusinessService. Provide it in WhatsAppClient config.",
27
+ "businessId"
28
+ );
29
+ }
30
+
31
+ // Create business client with Business Portfolio ID baked in
32
+ this.businessClient = new BusinessClient(
33
+ httpClient,
34
+ httpClient.businessId
35
+ );
36
+ }
37
+
38
+ /**
39
+ * List WhatsApp Business Accounts (WABAs) for a Business Portfolio
40
+ *
41
+ * @returns List of WABAs associated with the Business Portfolio
42
+ */
43
+ async listAccounts(): Promise<BusinessAccountsListResponse> {
44
+ return listAccounts(this.businessClient);
45
+ }
46
+ }
47
+
@@ -0,0 +1,3 @@
1
+ export { BusinessService } from "./BusinessService";
2
+ export type { BusinessAccountsListResponse } from "../../types/business/account";
3
+
@@ -0,0 +1,18 @@
1
+ import type { BusinessClient } from "../BusinessClient";
2
+ import type { BusinessAccountsListResponse } from "../../../types/business/account";
3
+
4
+ /**
5
+ * List WhatsApp Business Accounts (WABAs) for a Business Portfolio
6
+ *
7
+ * @param businessClient - Business client with Business Portfolio ID baked in
8
+ * @returns List of WABAs associated with the Business Portfolio
9
+ */
10
+ export async function listAccounts(
11
+ businessClient: BusinessClient
12
+ ): Promise<BusinessAccountsListResponse> {
13
+ // Make API request - businessClient handles the Business Portfolio ID prefix automatically
14
+ return businessClient.get<BusinessAccountsListResponse>(
15
+ "/whatsapp_business_accounts"
16
+ );
17
+ }
18
+
@@ -0,0 +1,2 @@
1
+ export * from "./messages/index";
2
+ export * from "./accounts/index";
@@ -0,0 +1,38 @@
1
+ import type { HttpClient } from "../../client/HttpClient";
2
+
3
+ /**
4
+ * Messages client - wraps HttpClient with phone number ID as base endpoint
5
+ *
6
+ * This client automatically prepends `/${phoneNumberId}` to all request paths,
7
+ * so methods can use relative paths like `/messages` instead of `/${phoneNumberId}/messages`.
8
+ *
9
+ * This treats phoneNumberId as a "client" for the messaging namespace - different
10
+ * phoneNumberIds represent different messaging endpoints.
11
+ */
12
+ export class MessagesClient {
13
+ constructor(
14
+ private readonly httpClient: HttpClient,
15
+ private readonly phoneNumberId: string
16
+ ) {}
17
+
18
+ /**
19
+ * Make a POST request with phone number ID prefix
20
+ */
21
+ async post<T>(path: string, body: unknown): Promise<T> {
22
+ return this.httpClient.post<T>(`/${this.phoneNumberId}${path}`, body);
23
+ }
24
+
25
+ /**
26
+ * Make a GET request with phone number ID prefix
27
+ */
28
+ async get<T>(path: string): Promise<T> {
29
+ return this.httpClient.get<T>(`/${this.phoneNumberId}${path}`);
30
+ }
31
+
32
+ /**
33
+ * Make a PATCH request with phone number ID prefix
34
+ */
35
+ async patch<T>(path: string, body: unknown): Promise<T> {
36
+ return this.httpClient.patch<T>(`/${this.phoneNumberId}${path}`, body);
37
+ }
38
+ }
@@ -0,0 +1,77 @@
1
+ import type { HttpClient } from "../../client/HttpClient";
2
+ import { sendText } from "./methods/send-text";
3
+ import { sendImage } from "./methods/send-image";
4
+ import { sendLocation } from "./methods/send-location";
5
+ import { sendReaction } from "./methods/send-reaction";
6
+ import { MessagesClient } from "./MessagesClient";
7
+ import { WhatsAppValidationError } from "../../errors";
8
+ import type {
9
+ SendTextRequest,
10
+ SendImageRequest,
11
+ SendLocationRequest,
12
+ SendReactionRequest,
13
+ } from "../../types/messages/request";
14
+ import type { MessageResponse } from "../../types/messages/response";
15
+
16
+ /**
17
+ * Messages service for sending WhatsApp messages
18
+ *
19
+ * The service validates that phoneNumberId is set at the client level and creates
20
+ * a MessagesClient instance. MessagesClient treats phoneNumberId as a "client" for
21
+ * the messaging namespace - different phoneNumberIds represent different endpoints.
22
+ */
23
+ export class MessagesService {
24
+ private readonly messagesClient: MessagesClient;
25
+
26
+ constructor(httpClient: HttpClient) {
27
+ // Validate that phoneNumberId is set at client level
28
+ if (!httpClient.phoneNumberId) {
29
+ throw new WhatsAppValidationError(
30
+ "phoneNumberId is required for MessagesService. Provide it in WhatsAppClient config.",
31
+ "phoneNumberId"
32
+ );
33
+ }
34
+
35
+ // Create messages client with phone number ID baked in
36
+ this.messagesClient = new MessagesClient(
37
+ httpClient,
38
+ httpClient.phoneNumberId
39
+ );
40
+ }
41
+
42
+ /**
43
+ * Send a text message
44
+ *
45
+ * @param request - Text message request (to, text)
46
+ */
47
+ async sendText(request: SendTextRequest): Promise<MessageResponse> {
48
+ return sendText(this.messagesClient, request);
49
+ }
50
+
51
+ /**
52
+ * Send an image message
53
+ *
54
+ * @param request - Image message request (to, image)
55
+ */
56
+ async sendImage(request: SendImageRequest): Promise<MessageResponse> {
57
+ return sendImage(this.messagesClient, request);
58
+ }
59
+
60
+ /**
61
+ * Send a location message
62
+ *
63
+ * @param request - Location message request (to, location)
64
+ */
65
+ async sendLocation(request: SendLocationRequest): Promise<MessageResponse> {
66
+ return sendLocation(this.messagesClient, request);
67
+ }
68
+
69
+ /**
70
+ * Send a reaction message
71
+ *
72
+ * @param request - Reaction message request (to, reaction)
73
+ */
74
+ async sendReaction(request: SendReactionRequest): Promise<MessageResponse> {
75
+ return sendReaction(this.messagesClient, request);
76
+ }
77
+ }
@@ -0,0 +1,8 @@
1
+ export { MessagesService } from "./MessagesService";
2
+ export type {
3
+ SendTextRequest,
4
+ SendImageRequest,
5
+ SendLocationRequest,
6
+ SendReactionRequest,
7
+ } from "../../types/messages/request";
8
+ export type { MessageResponse } from "../../types/messages/response";
@@ -0,0 +1,33 @@
1
+ import type { MessagesClient } from "../MessagesClient";
2
+ import { sendImageRequestSchema } from "../../../schemas/messages/request";
3
+ import type { SendImageRequest } from "../../../types/messages/request";
4
+ import type { MessageResponse } from "../../../types/messages/response";
5
+ import { buildMessagePayload } from "../utils/build-message-payload";
6
+ import { transformZodError } from "../../../utils/zod-error";
7
+
8
+ /**
9
+ * Send an image message
10
+ *
11
+ * @param messagesClient - Messages client with phone number ID baked in
12
+ * @param request - Image message request (to, image)
13
+ */
14
+ export async function sendImage(
15
+ messagesClient: MessagesClient,
16
+ request: SendImageRequest
17
+ ): Promise<MessageResponse> {
18
+ // Validate request with schema - throws WhatsAppValidationError if invalid
19
+ const result = sendImageRequestSchema.safeParse(request);
20
+ if (!result.success) {
21
+ throw transformZodError(result.error);
22
+ }
23
+ const data = result.data;
24
+
25
+ // Build message payload using common structure
26
+ // The request.image already matches the API structure, so we can pass it directly
27
+ const payload = buildMessagePayload(data.to, "image", {
28
+ image: data.image,
29
+ });
30
+
31
+ // Make API request - messagesClient handles the phoneNumberId prefix automatically
32
+ return messagesClient.post<MessageResponse>("/messages", payload);
33
+ }
@@ -0,0 +1,32 @@
1
+ import type { MessagesClient } from "../MessagesClient";
2
+ import { sendLocationRequestSchema } from "../../../schemas/messages/request";
3
+ import type { SendLocationRequest } from "../../../types/messages/request";
4
+ import type { MessageResponse } from "../../../types/messages/response";
5
+ import { buildMessagePayload } from "../utils/build-message-payload";
6
+ import { transformZodError } from "../../../utils/zod-error";
7
+
8
+ /**
9
+ * Send a location message
10
+ *
11
+ * @param messagesClient - Messages client with phone number ID baked in
12
+ * @param request - Location message request (to, location)
13
+ */
14
+ export async function sendLocation(
15
+ messagesClient: MessagesClient,
16
+ request: SendLocationRequest
17
+ ): Promise<MessageResponse> {
18
+ // Validate request with schema - throws WhatsAppValidationError if invalid
19
+ const result = sendLocationRequestSchema.safeParse(request);
20
+ if (!result.success) {
21
+ throw transformZodError(result.error);
22
+ }
23
+ const data = result.data;
24
+
25
+ // Build message payload using common structure
26
+ const payload = buildMessagePayload(data.to, "location", {
27
+ location: data.location,
28
+ });
29
+
30
+ // Make API request - messagesClient handles the phoneNumberId prefix automatically
31
+ return messagesClient.post<MessageResponse>("/messages", payload);
32
+ }
@@ -0,0 +1,33 @@
1
+ import type { MessagesClient } from "../MessagesClient";
2
+ import { sendReactionRequestSchema } from "../../../schemas/messages/request";
3
+ import type { SendReactionRequest } from "../../../types/messages/request";
4
+ import type { MessageResponse } from "../../../types/messages/response";
5
+ import { buildMessagePayload } from "../utils/build-message-payload";
6
+ import { transformZodError } from "../../../utils/zod-error";
7
+
8
+ /**
9
+ * Send a reaction message
10
+ *
11
+ * @param messagesClient - Messages client with phone number ID baked in
12
+ * @param request - Reaction message request (to, reaction)
13
+ */
14
+ export async function sendReaction(
15
+ messagesClient: MessagesClient,
16
+ request: SendReactionRequest
17
+ ): Promise<MessageResponse> {
18
+ // Validate request with schema - throws WhatsAppValidationError if invalid
19
+ const result = sendReactionRequestSchema.safeParse(request);
20
+ if (!result.success) {
21
+ throw transformZodError(result.error);
22
+ }
23
+ const data = result.data;
24
+
25
+ // Build message payload using common structure
26
+ const payload = buildMessagePayload(data.to, "reaction", {
27
+ reaction: data.reaction,
28
+ });
29
+
30
+ // Make API request - messagesClient handles the phoneNumberId prefix automatically
31
+ return messagesClient.post<MessageResponse>("/messages", payload);
32
+ }
33
+