mp-js-api 0.0.30 → 0.0.31

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.
package/tasks/todo.md ADDED
@@ -0,0 +1,617 @@
1
+ # MinistryPlatform Communications API & Stored Procedures Implementation Plan
2
+
3
+ ## API Endpoints Summary (from Swagger)
4
+
5
+ | Endpoint | Method | Description |
6
+ |----------|--------|-------------|
7
+ | `POST /communications` | POST | Creates new communication and immediately renders/schedules for delivery |
8
+ | `GET /communications/{communicationId}` | GET | Returns communication by ID |
9
+ | `DELETE /communications/{communicationId}` | DELETE | Deletes communication by ID |
10
+ | `POST /messages` | POST | Creates emails from provided info and schedules for delivery |
11
+ | `POST /texts` | POST | Creates SMS/MMS messages and schedules for delivery |
12
+ | `GET /procs` | GET | Returns list of available procedures with metadata |
13
+ | `GET /procs/{procedure}` | GET | Executes stored procedure with query string params |
14
+ | `POST /procs/{procedure}` | POST | Executes stored procedure with body params |
15
+
16
+ ---
17
+
18
+ ## 1. Communications Endpoint (`POST /communications`)
19
+
20
+ ### Request Format
21
+ Supports both JSON and multipart/form-data (for attachments).
22
+
23
+ **CommunicationInfo Request Body:**
24
+ ```typescript
25
+ interface CommunicationInfo {
26
+ AuthorUserId: number; // Required - User ID who authored
27
+ Subject: string; // Required - Email subject or SMS content
28
+ Body: string; // Email body content (HTML supported)
29
+ FromContactId: number; // Required - Contact ID of sender
30
+ ReplyToContactId: number; // Required - Contact ID for replies
31
+ CommunicationType: CommunicationType; // Required - 'Email' | 'SMS'
32
+ Contacts: number[]; // Required - Array of recipient Contact IDs
33
+ StartDate?: string; // ISO date - when to send (future = scheduled)
34
+ IsBulkEmail?: boolean; // Default false
35
+ SendToContactParents?: boolean; // Send to household heads
36
+ TextPhoneNumberId?: number; // Required for SMS - outbound phone ID
37
+ }
38
+
39
+ type CommunicationType = 'Unknown' | 'Email' | 'SMS' | 'RssFeed' | 'GlobalMFA';
40
+ ```
41
+
42
+ **Example Request:**
43
+ ```json
44
+ {
45
+ "AuthorUserId": 4,
46
+ "Body": "Test Body",
47
+ "FromContactId": 2,
48
+ "ReplyToContactId": 2,
49
+ "CommunicationType": "Email",
50
+ "Contacts": [200145, 200109, 200536],
51
+ "IsBulkEmail": false,
52
+ "SendToContactParents": false,
53
+ "Subject": "Test Subject",
54
+ "StartDate": "2017-01-01T00:00:00",
55
+ "TextPhoneNumberId": 1
56
+ }
57
+ ```
58
+
59
+ ### Response Model (Communication)
60
+ ```typescript
61
+ interface Communication {
62
+ CommunicationId: number; // Unique identifier
63
+ AuthorUserId: number; // User ID who authored
64
+ AuthorName: string; // Display name of author (readonly)
65
+ Subject: string; // Subject line / SMS content
66
+ Body?: string; // Body content
67
+ StartDate: string; // ISO datetime
68
+ ExpirationDate?: string; // Optional expiration
69
+ Status?: CommunicationStatus; // Current status
70
+ FromContactId: number; // Sender contact ID
71
+ FromName: string; // Sender display name (readonly)
72
+ FromAddress: string; // Sender email (readonly)
73
+ ReplyToContactId: number; // Reply-to contact ID
74
+ ReplyToName: string; // Reply-to name (readonly)
75
+ ReplyToAddress: string; // Reply-to email (readonly)
76
+ TaskId?: number; // Associated task ID
77
+ SelectionId?: number; // Selection ID for recipients
78
+ RecipientContactId?: number; // Single recipient override
79
+ RecipientName?: string; // Recipient name (readonly)
80
+ SendToContactParents?: boolean; // Send to household heads
81
+ ExcludeOptedOutOfBulkMessages?: boolean; // Exclude opted-out
82
+ TextingComplianceLevel?: TextingComplianceLevel;
83
+ TimeZoneName?: string; // Timezone for date rendering
84
+ CultureName?: string; // Locale for formatting
85
+ TextPhoneNumberId?: number; // Outbound SMS phone ID
86
+ TextPhoneNumber?: string; // Outbound phone (readonly)
87
+ CommunicationType: CommunicationType;
88
+ AlternateEmailTypeId?: number; // Alternate email type
89
+ PublicationId?: number; // Related publication
90
+ }
91
+
92
+ type CommunicationStatus = 'Unknown' | 'Draft' | 'InReview' | 'ReadyToSend' | 'Sent';
93
+ type TextingComplianceLevel = 'None' | 'SingleOptIn' | 'DoubleOptIn';
94
+ ```
95
+
96
+ ---
97
+
98
+ ## 2. Messages Endpoint (`POST /messages`)
99
+
100
+ ### Request Format
101
+ Supports multipart/form-data for attachments.
102
+
103
+ **MessageInfo Request Body:**
104
+ ```typescript
105
+ interface MessageInfo {
106
+ FromAddress: MessageAddress; // Required - Sender info
107
+ ToAddresses: MessageAddress[]; // Required - Recipients
108
+ ReplyToAddress?: MessageAddress; // Reply-to info
109
+ Subject: string; // Required - Email subject
110
+ Body: string; // Required - Email body (HTML)
111
+ StartDate?: string; // ISO date for scheduling
112
+ }
113
+
114
+ interface MessageAddress {
115
+ DisplayName: string; // Display name
116
+ Address: string; // Email address
117
+ }
118
+ ```
119
+
120
+ **Example Request:**
121
+ ```json
122
+ {
123
+ "FromAddress": { "DisplayName": "From Name", "Address": "from@test.com" },
124
+ "ToAddresses": [
125
+ { "DisplayName": "To Address 1", "Address": "to1@test.com" },
126
+ { "DisplayName": "To Address 2", "Address": "to2@test.com" }
127
+ ],
128
+ "ReplyToAddress": { "DisplayName": "Reply Address", "Address": "reply@test.com" },
129
+ "Subject": "Test Subject",
130
+ "Body": "Test Body"
131
+ }
132
+ ```
133
+
134
+ ### Response
135
+ Returns `Communication` object (same as `/communications` endpoint).
136
+
137
+ ---
138
+
139
+ ## 3. Texts Endpoint (`POST /texts`)
140
+
141
+ ### Request Format
142
+ Supports multipart/form-data for MMS attachments.
143
+
144
+ **TextInfo Request Body:**
145
+ ```typescript
146
+ interface TextInfo {
147
+ FromPhoneNumberId: number; // Required - MP phone number ID
148
+ ToPhoneNumbers: string[]; // Required - E.164 format (+1XXXXXXXXXX)
149
+ Message: string; // Required - SMS/MMS content
150
+ StartDate?: string; // ISO date for scheduling
151
+ }
152
+ ```
153
+
154
+ **Example Request:**
155
+ ```json
156
+ {
157
+ "FromPhoneNumberId": 1,
158
+ "Message": "Test Body",
159
+ "StartDate": "2017-01-01T00:00:00",
160
+ "ToPhoneNumbers": [
161
+ "+13362702467",
162
+ "+16781231234"
163
+ ]
164
+ }
165
+ ```
166
+
167
+ ### Response
168
+ Returns `Communication` object (same as `/communications` endpoint).
169
+
170
+ ---
171
+
172
+ ## 4. Procedures Endpoints
173
+
174
+ ### `GET /procs` - List Available Procedures
175
+
176
+ **Query Parameters:**
177
+ - `$search` (string, optional) - Filter procedures by name
178
+
179
+ **Response:**
180
+ ```typescript
181
+ interface ProcedureInfo {
182
+ Name: string; // Procedure name (e.g., "api_MyProc")
183
+ Parameters: ParameterInfo[]; // Parameter definitions
184
+ }
185
+
186
+ interface ParameterInfo {
187
+ Name: string; // Parameter name (e.g., "@ContactId")
188
+ Direction: ParameterDirection; // Input, Output, etc.
189
+ DataType: ParameterDataType; // String, Integer32, etc.
190
+ Size: number; // Max length
191
+ }
192
+
193
+ type ParameterDirection = 'Input' | 'Output' | 'InputOutput' | 'ReturnValue';
194
+ type ParameterDataType =
195
+ | 'Unknown' | 'String' | 'Text' | 'Xml' | 'Byte'
196
+ | 'Integer16' | 'Integer32' | 'Integer64'
197
+ | 'Decimal' | 'Real' | 'Boolean'
198
+ | 'Date' | 'Time' | 'DateTime' | 'Timestamp'
199
+ | 'Binary' | 'Password' | 'Money' | 'Guid'
200
+ | 'Phone' | 'Email' | 'Variant' | 'Separator'
201
+ | 'Image' | 'Counter' | 'TableName' | 'GlobalFilter'
202
+ | 'TimeZone' | 'Locale' | 'LargeString' | 'Url'
203
+ | 'Strings' | 'Integers' | 'Color' | 'SecretKey';
204
+ ```
205
+
206
+ ### `GET /procs/{procedure}` - Execute with Query Params
207
+
208
+ **Path Parameters:**
209
+ - `procedure` (string, required) - Procedure name
210
+
211
+ **Query Parameters:**
212
+ - Any procedure parameters as query string (e.g., `?@ContactId=123`)
213
+
214
+ ### `POST /procs/{procedure}` - Execute with Body
215
+
216
+ **Path Parameters:**
217
+ - `procedure` (string, required) - Procedure name
218
+
219
+ **Request Body:**
220
+ ```typescript
221
+ // Key-value pairs matching procedure parameters
222
+ interface ProcedureInput {
223
+ [parameterName: string]: any; // e.g., { "@SelectionID": 26918 }
224
+ }
225
+ ```
226
+
227
+ **Response:**
228
+ Returns array of result sets (each is array of objects):
229
+ ```typescript
230
+ type ProcedureResult = object[][];
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Implementation Plan
236
+
237
+ ### Phase 1: Core Types (`src/types/`)
238
+
239
+ > **IMPORTANT:** These endpoints use **PascalCase** in the API (not Snake_Case like `/tables/`).
240
+ > We use camelCase internally (consistent with library), and convert to PascalCase before sending.
241
+ > Need to add `convertToPascalCase()` utility in `src/utils/converters.ts`.
242
+
243
+ #### 1.1 Create `src/types/communications.ts`
244
+ ```typescript
245
+ // CommunicationInfo - Request payload (camelCase for library usage)
246
+ // API expects: AuthorUserId, FromContactId, etc. (PascalCase)
247
+ export interface CommunicationInfo {
248
+ authorUserId: number;
249
+ subject: string;
250
+ body?: string;
251
+ fromContactId: number;
252
+ replyToContactId: number;
253
+ communicationType: CommunicationType;
254
+ contacts: number[];
255
+ startDate?: string;
256
+ isBulkEmail?: boolean;
257
+ sendToContactParents?: boolean;
258
+ textPhoneNumberId?: number;
259
+ }
260
+
261
+ // Communication - Response model (camelCase for library usage)
262
+ // API returns: CommunicationId, AuthorUserId, etc. (PascalCase)
263
+ export interface Communication {
264
+ communicationId: number;
265
+ authorUserId: number;
266
+ authorName: string;
267
+ subject: string;
268
+ body?: string;
269
+ startDate: string;
270
+ expirationDate?: string;
271
+ status?: CommunicationStatus;
272
+ fromContactId: number;
273
+ fromName: string;
274
+ fromAddress: string;
275
+ replyToContactId: number;
276
+ replyToName: string;
277
+ replyToAddress: string;
278
+ taskId?: number;
279
+ selectionId?: number;
280
+ recipientContactId?: number;
281
+ recipientName?: string;
282
+ sendToContactParents?: boolean;
283
+ excludeOptedOutOfBulkMessages?: boolean;
284
+ textingComplianceLevel?: TextingComplianceLevel;
285
+ timeZoneName?: string;
286
+ cultureName?: string;
287
+ textPhoneNumberId?: number;
288
+ textPhoneNumber?: string;
289
+ communicationType: CommunicationType;
290
+ alternateEmailTypeId?: number;
291
+ publicationId?: number;
292
+ }
293
+
294
+ export type CommunicationType = 'Unknown' | 'Email' | 'SMS' | 'RssFeed' | 'GlobalMFA';
295
+ export type CommunicationStatus = 'Unknown' | 'Draft' | 'InReview' | 'ReadyToSend' | 'Sent';
296
+ export type TextingComplianceLevel = 'None' | 'SingleOptIn' | 'DoubleOptIn';
297
+ ```
298
+
299
+ #### 1.2 Create `src/types/messages.ts`
300
+ ```typescript
301
+ // MessageAddress - nested object (camelCase for library usage)
302
+ // API expects: DisplayName, Address (PascalCase)
303
+ export interface MessageAddress {
304
+ displayName: string;
305
+ address: string;
306
+ }
307
+
308
+ // MessageInfo - Request payload (camelCase for library usage)
309
+ // API expects: FromAddress, ToAddresses, etc. (PascalCase)
310
+ export interface MessageInfo {
311
+ fromAddress: MessageAddress;
312
+ toAddresses: MessageAddress[];
313
+ replyToAddress?: MessageAddress;
314
+ subject: string;
315
+ body: string;
316
+ startDate?: string;
317
+ }
318
+ ```
319
+
320
+ #### 1.3 Create `src/types/texts.ts`
321
+ ```typescript
322
+ // TextInfo - Request payload (camelCase for library usage)
323
+ // API expects: FromPhoneNumberId, ToPhoneNumbers, Message (PascalCase)
324
+ export interface TextInfo {
325
+ fromPhoneNumberId: number;
326
+ toPhoneNumbers: string[];
327
+ message: string;
328
+ startDate?: string;
329
+ }
330
+ ```
331
+
332
+ #### 1.4 Create `src/types/procedures.ts`
333
+ ```typescript
334
+ // ProcedureInfo - Response from GET /procs (camelCase for library usage)
335
+ // API returns: Name, Parameters (PascalCase)
336
+ export interface ProcedureInfo {
337
+ name: string;
338
+ parameters: ParameterInfo[];
339
+ }
340
+
341
+ export interface ParameterInfo {
342
+ name: string;
343
+ direction: ParameterDirection;
344
+ dataType: ParameterDataType;
345
+ size: number;
346
+ }
347
+
348
+ export type ParameterDirection = 'Input' | 'Output' | 'InputOutput' | 'ReturnValue';
349
+ export type ParameterDataType =
350
+ | 'Unknown' | 'String' | 'Text' | 'Xml' | 'Byte'
351
+ | 'Integer16' | 'Integer32' | 'Integer64'
352
+ | 'Decimal' | 'Real' | 'Boolean'
353
+ | 'Date' | 'Time' | 'DateTime' | 'Timestamp'
354
+ | 'Binary' | 'Password' | 'Money' | 'Guid'
355
+ | 'Phone' | 'Email' | 'Variant' | 'Separator'
356
+ | 'Image' | 'Counter' | 'TableName' | 'GlobalFilter'
357
+ | 'TimeZone' | 'Locale' | 'LargeString' | 'Url'
358
+ | 'Strings' | 'Integers' | 'Color' | 'SecretKey';
359
+ ```
360
+
361
+ #### 1.5 Add to `src/utils/converters.ts`
362
+ ```typescript
363
+ /**
364
+ * Converts camelCase object keys to PascalCase.
365
+ * Used for /communications, /messages, /texts endpoints.
366
+ * Example: { authorUserId: 1 } → { AuthorUserId: 1 }
367
+ */
368
+ export function convertToPascalCase<T>(obj: Record<string, any>): T {
369
+ // Implementation: capitalize first letter of each key
370
+ // Handle nested objects and arrays recursively
371
+ }
372
+
373
+ /**
374
+ * Converts PascalCase object keys to camelCase.
375
+ * Used for responses from /communications, /messages, /texts, /procs endpoints.
376
+ * Example: { AuthorUserId: 1 } → { authorUserId: 1 }
377
+ */
378
+ export function convertFromPascalCase<T>(obj: Record<string, any>): T {
379
+ // Implementation: lowercase first letter of each key
380
+ // Handle nested objects and arrays recursively
381
+ }
382
+ ```
383
+
384
+ ---
385
+
386
+ ### Phase 2: API Methods (`src/api.ts`)
387
+
388
+ #### 2.1 Add New Type Definitions
389
+ ```typescript
390
+ export type APISendCommunicationInstance = (
391
+ params: APISendCommunicationParameter
392
+ ) => Promise<Communication | { error: ErrorDetails }>;
393
+
394
+ export type APISendMessageInstance = (
395
+ params: APISendMessageParameter
396
+ ) => Promise<Communication | { error: ErrorDetails }>;
397
+
398
+ export type APISendTextInstance = (
399
+ params: APISendTextParameter
400
+ ) => Promise<Communication | { error: ErrorDetails }>;
401
+
402
+ export type APIExecuteProcedureInstance = <T extends Record<string, any>>(
403
+ params: APIExecuteProcedureParameter
404
+ ) => Promise<T[][] | { error: ErrorDetails }>;
405
+
406
+ export type APIGetProceduresInstance = (
407
+ search?: string
408
+ ) => Promise<ProcedureInfo[] | { error: ErrorDetails }>;
409
+ ```
410
+
411
+ #### 2.2 Add Core Methods
412
+ ```typescript
413
+ // POST /communications
414
+ const sendCommunication = async ({ data, config }) => {
415
+ const url = '/communications';
416
+ const payload = convertToPascalCase(data); // camelCase → PascalCase
417
+ const res = await post(url, payload, config);
418
+ return convertFromPascalCase(res.data); // PascalCase → camelCase
419
+ };
420
+
421
+ // POST /messages
422
+ const sendMessage = async ({ data, config }) => {
423
+ const url = '/messages';
424
+ const payload = convertToPascalCase(data); // camelCase → PascalCase
425
+ const res = await post(url, payload, config);
426
+ return convertFromPascalCase(res.data); // PascalCase → camelCase
427
+ };
428
+
429
+ // POST /texts
430
+ const sendText = async ({ data, config }) => {
431
+ const url = '/texts';
432
+ const payload = convertToPascalCase(data); // camelCase → PascalCase
433
+ const res = await post(url, payload, config);
434
+ return convertFromPascalCase(res.data); // PascalCase → camelCase
435
+ };
436
+
437
+ // GET /procs
438
+ const getProcedures = async (search?: string) => {
439
+ const url = search ? `/procs?$search=${search}` : '/procs';
440
+ const res = await get(url);
441
+ return res.data.map(p => convertFromPascalCase(p)); // PascalCase → camelCase
442
+ };
443
+
444
+ // POST /procs/{procedure}
445
+ const executeProcedure = async <T>({ procedureName, input, config }) => {
446
+ const url = `/procs/${procedureName}`;
447
+ // Note: input params use @ParamName format, no conversion needed
448
+ const res = await post(url, input, config);
449
+ return res.data; // Returns T[][] - results may need conversion depending on proc
450
+ };
451
+ ```
452
+
453
+ ---
454
+
455
+ ### Phase 3: Public API (`src/index.ts`)
456
+
457
+ #### 3.1 Update MPInstance Interface
458
+ ```typescript
459
+ export type MPInstance = {
460
+ // ... existing methods ...
461
+
462
+ // Communications
463
+ sendCommunication(
464
+ data: CreateCommunicationPayload
465
+ ): Promise<Communication | { error: ErrorDetails }>;
466
+
467
+ // Messages (by email address)
468
+ sendMessage(
469
+ data: CreateMessagePayload
470
+ ): Promise<Communication | { error: ErrorDetails }>;
471
+
472
+ // Texts (by phone number)
473
+ sendText(
474
+ data: CreateTextPayload
475
+ ): Promise<Communication | { error: ErrorDetails }>;
476
+
477
+ // Procedures
478
+ getProcedures(
479
+ search?: string
480
+ ): Promise<ProcedureInfo[] | { error: ErrorDetails }>;
481
+
482
+ executeProcedure<T extends Record<string, any>>(
483
+ procedureName: string,
484
+ input?: Record<string, any>
485
+ ): Promise<T[][] | { error: ErrorDetails }>;
486
+ };
487
+ ```
488
+
489
+ #### 3.2 Create Payload Types
490
+ ```typescript
491
+ // camelCase internally, converted to PascalCase before API call
492
+ export type CreateCommunicationPayload = WithRequired<
493
+ CommunicationInfo,
494
+ 'authorUserId' | 'subject' | 'fromContactId' | 'replyToContactId' | 'communicationType' | 'contacts'
495
+ >;
496
+
497
+ export type CreateMessagePayload = WithRequired<
498
+ MessageInfo,
499
+ 'fromAddress' | 'toAddresses' | 'subject' | 'body'
500
+ >;
501
+
502
+ export type CreateTextPayload = WithRequired<
503
+ TextInfo,
504
+ 'fromPhoneNumberId' | 'toPhoneNumbers' | 'message'
505
+ >;
506
+ ```
507
+
508
+ #### 3.3 Add Exports
509
+ ```typescript
510
+ export {
511
+ // ... existing exports ...
512
+ Communication,
513
+ CommunicationInfo,
514
+ CommunicationType,
515
+ CommunicationStatus,
516
+ MessageInfo,
517
+ MessageAddress,
518
+ TextInfo,
519
+ ProcedureInfo,
520
+ ParameterInfo,
521
+ ParameterDirection,
522
+ ParameterDataType,
523
+ };
524
+ ```
525
+
526
+ ---
527
+
528
+ ## File Changes Summary
529
+
530
+ | File | Action | Description |
531
+ |------|--------|-------------|
532
+ | `src/types/communications.ts` | **Create** | Communication types and enums |
533
+ | `src/types/messages.ts` | **Create** | Message and MessageAddress types |
534
+ | `src/types/texts.ts` | **Create** | TextInfo types |
535
+ | `src/types/procedures.ts` | **Create** | Procedure and Parameter types |
536
+ | `src/utils/converters.ts` | **Modify** | Add `convertToPascalCase()` and `convertFromPascalCase()` |
537
+ | `src/api.ts` | **Modify** | Add 5 new methods |
538
+ | `src/index.ts` | **Modify** | Export new methods and types |
539
+
540
+ ---
541
+
542
+ ## Implementation Notes
543
+
544
+ ### Case Conversion
545
+ The Communications/Messages/Texts/Procs endpoints use **PascalCase** in request/response bodies (not Snake_Case like `/tables/`).
546
+
547
+ | Endpoint Type | API Format | Library Format | Conversion |
548
+ |---------------|------------|----------------|------------|
549
+ | `/tables/*` | Snake_Case | camelCase | `convertToSnakeCase()` / `convertToCamelCase()` |
550
+ | `/communications`, `/messages`, `/texts`, `/procs` | PascalCase | camelCase | `convertToPascalCase()` / `convertFromPascalCase()` |
551
+
552
+ **Implementation:** Add two new utility functions to `src/utils/converters.ts`:
553
+ - `convertToPascalCase()` - for outgoing requests
554
+ - `convertFromPascalCase()` - for incoming responses (same as existing `convertToCamelCase()` for first letter)
555
+
556
+ ### Multipart Form Data Support
557
+ All three communication endpoints support file attachments via multipart/form-data. For the initial implementation, we can support JSON-only requests. File attachment support can be added later if needed.
558
+
559
+ ### Scheduling
560
+ Setting `StartDate` to a future date schedules the communication for later delivery. If omitted or set to current time, sends immediately.
561
+
562
+ ---
563
+
564
+ ## Usage Examples
565
+
566
+ ### Send Email via Communications API
567
+ ```typescript
568
+ const mp = createMPInstance({ auth });
569
+
570
+ const result = await mp.sendCommunication({
571
+ authorUserId: 4,
572
+ fromContactId: 100,
573
+ replyToContactId: 100,
574
+ subject: 'Event Reminder',
575
+ body: '<h1>Reminder</h1><p>Service starts tomorrow!</p>',
576
+ communicationType: 'Email',
577
+ contacts: [1001, 1002, 1003],
578
+ startDate: '2024-01-15T09:00:00' // Schedule for Jan 15
579
+ });
580
+ ```
581
+
582
+ ### Send Email via Messages API
583
+ ```typescript
584
+ const result = await mp.sendMessage({
585
+ fromAddress: { displayName: 'Church Admin', address: 'admin@church.org' },
586
+ toAddresses: [
587
+ { displayName: 'John Doe', address: 'john@email.com' }
588
+ ],
589
+ replyToAddress: { displayName: 'Support', address: 'support@church.org' },
590
+ subject: 'Welcome!',
591
+ body: '<h1>Welcome to our church!</h1>'
592
+ });
593
+ ```
594
+
595
+ ### Send SMS via Texts API
596
+ ```typescript
597
+ const result = await mp.sendText({
598
+ fromPhoneNumberId: 1, // Your MP phone number ID
599
+ toPhoneNumbers: ['+15551234567', '+15559876543'],
600
+ message: 'Service starts in 30 minutes!'
601
+ });
602
+ ```
603
+
604
+ ### Execute Stored Procedure
605
+ ```typescript
606
+ // List available procedures
607
+ const procs = await mp.getProcedures('api_');
608
+
609
+ // Execute procedure with parameters
610
+ const result = await mp.executeProcedure<ContactResult>(
611
+ 'api_Custom_GetActiveMembers',
612
+ { '@MinistryID': 5, '@StartDate': '2024-01-01' }
613
+ );
614
+
615
+ // Result is array of result sets
616
+ const contacts = result[0]; // First result set
617
+ ```