# SendGrid Client

Send transactional emails, manage contacts, and interact with SendGrid's email APIs.

## Methods

| Method                                   | Description                                          |
| ---------------------------------------- | ---------------------------------------------------- |
| `apiRequest(options, schema, metadata?)` | Make any SendGrid API request with schema validation |

## Usage

### Send a Simple Email

```typescript
import { api, z, sendGrid } from "@superblocksteam/sdk-api";

// Integration ID from the integrations panel
const PROD_SENDGRID = "a1b2c3d4-5678-90ab-cdef-sendgrid0001";

// SendGrid returns 202 Accepted with empty body on success
const SendEmailResponseSchema = z.object({}).optional();

export default api({
  integrations: {
    sendgrid: sendGrid(PROD_SENDGRID),
  },
  name: "SendGridExample",
  input: z.object({
    to: z.string().email(),
    subject: z.string(),
    body: z.string(),
  }),
  output: z.object({
    success: z.boolean(),
  }),
  async run(ctx, { to, subject, body }) {
    await ctx.integrations.sendgrid.apiRequest(
      {
        method: "POST",
        path: "/v3/mail/send",
        body: {
          personalizations: [
            {
              to: [{ email: to }],
            },
          ],
          from: { email: "noreply@yourcompany.com", name: "Your Company" },
          subject: subject,
          content: [
            {
              type: "text/plain",
              value: body,
            },
          ],
        },
      },
      { response: SendEmailResponseSchema },
    );

    return { success: true };
  },
});
```

### Send HTML Email

```typescript
await ctx.integrations.sendgrid.apiRequest(
  {
    method: "POST",
    path: "/v3/mail/send",
    body: {
      personalizations: [
        {
          to: [{ email: "user@example.com" }],
        },
      ],
      from: { email: "noreply@yourcompany.com" },
      subject: "Welcome!",
      content: [
        {
          type: "text/plain",
          value: "Welcome to our service!",
        },
        {
          type: "text/html",
          value: "<h1>Welcome!</h1><p>Thank you for signing up.</p>",
        },
      ],
    },
  },
  { response: SendEmailResponseSchema },
);
```

### Send with Template

```typescript
await ctx.integrations.sendgrid.apiRequest(
  {
    method: "POST",
    path: "/v3/mail/send",
    body: {
      personalizations: [
        {
          to: [{ email: "user@example.com" }],
          dynamic_template_data: {
            first_name: "John",
            order_id: "12345",
            order_total: "$99.99",
          },
        },
      ],
      from: { email: "orders@yourcompany.com" },
      template_id: "d-abc123def456", // Your template ID
    },
  },
  { response: SendEmailResponseSchema },
);
```

### Send to Multiple Recipients

```typescript
await ctx.integrations.sendgrid.apiRequest(
  {
    method: "POST",
    path: "/v3/mail/send",
    body: {
      personalizations: [
        {
          to: [
            { email: "user1@example.com", name: "User One" },
            { email: "user2@example.com", name: "User Two" },
          ],
          cc: [{ email: "cc@example.com" }],
          bcc: [{ email: "bcc@example.com" }],
        },
      ],
      from: { email: "noreply@yourcompany.com" },
      subject: "Team Update",
      content: [{ type: "text/plain", value: "Hello team!" }],
    },
  },
  { response: SendEmailResponseSchema },
);
```

### Send with Attachments

```typescript
await ctx.integrations.sendgrid.apiRequest(
  {
    method: "POST",
    path: "/v3/mail/send",
    body: {
      personalizations: [{ to: [{ email: "user@example.com" }] }],
      from: { email: "reports@yourcompany.com" },
      subject: "Your Report",
      content: [{ type: "text/plain", value: "Please find attached." }],
      attachments: [
        {
          content: base64EncodedContent, // Base64 encoded file
          filename: "report.pdf",
          type: "application/pdf",
          disposition: "attachment",
        },
      ],
    },
  },
  { response: SendEmailResponseSchema },
);
```

### Add Contact to List

```typescript
const AddContactResponseSchema = z.object({
  job_id: z.string(),
});

const result = await ctx.integrations.sendgrid.apiRequest(
  {
    method: "PUT",
    path: "/v3/marketing/contacts",
    body: {
      list_ids: ["abc123-list-id"],
      contacts: [
        {
          email: "newuser@example.com",
          first_name: "John",
          last_name: "Doe",
          custom_fields: {
            signup_source: "website",
          },
        },
      ],
    },
  },
  { response: AddContactResponseSchema },
);
```

### Get Email Statistics

```typescript
const StatsResponseSchema = z.array(
  z.object({
    date: z.string(),
    stats: z.array(
      z.object({
        metrics: z.object({
          requests: z.number(),
          delivered: z.number(),
          opens: z.number(),
          clicks: z.number(),
          bounces: z.number(),
          spam_reports: z.number(),
        }),
      }),
    ),
  }),
);

const stats = await ctx.integrations.sendgrid.apiRequest(
  {
    method: "GET",
    path: "/v3/stats",
    params: {
      start_date: "2024-01-01",
      end_date: "2024-01-31",
      aggregated_by: "day",
    },
  },
  { response: StatsResponseSchema },
);

stats.forEach((day) => {
  const metrics = day.stats[0]?.metrics;
  console.log(
    `${day.date}: ${metrics?.delivered} delivered, ${metrics?.opens} opens`,
  );
});
```

## Trace Metadata

All methods accept an optional `metadata` parameter as the last argument for diagnostics labeling. See the [root SDK README](../../../README.md#trace-metadata) for details.

## Common Pitfalls

### No Specialized Methods

```typescript
// WRONG - These methods do not exist
await sendgrid.sendEmail({ ... });
await sendgrid.send({ ... });

// CORRECT - Use apiRequest
await ctx.integrations.sendgrid.apiRequest(
  { method: "POST", path: "/v3/mail/send", body: { ... } },
  { response: SendEmailResponseSchema }
);
```

### Empty Response Body

The mail/send endpoint returns 202 with no body on success:

```typescript
// Schema should handle empty response
const SendEmailResponseSchema = z.object({}).optional();

// Or use z.unknown() for flexibility
const SendEmailResponseSchema = z.unknown();
```

### Personalizations Structure

SendGrid uses a `personalizations` array, not simple `to` field:

```typescript
// WRONG - Simple to field
const body = {
  to: "user@example.com",
  from: "sender@example.com",
  subject: "Hello",
};

// CORRECT - Personalizations array
const body = {
  personalizations: [
    {
      to: [{ email: "user@example.com" }],
    },
  ],
  from: { email: "sender@example.com" },
  subject: "Hello",
};
```

### Email Objects vs Strings

Use email objects, not plain strings:

```typescript
// WRONG - Plain strings
const body = {
  personalizations: [{ to: ["user@example.com"] }],
  from: "sender@example.com",
};

// CORRECT - Email objects
const body = {
  personalizations: [
    {
      to: [{ email: "user@example.com", name: "User Name" }],
    },
  ],
  from: { email: "sender@example.com", name: "Sender Name" },
};
```

### Content Array Order

Include both text and HTML content for best deliverability:

```typescript
const body = {
  content: [
    { type: "text/plain", value: "Plain text version" }, // First
    { type: "text/html", value: "<p>HTML version</p>" }, // Second
  ],
};
```

### Rate Limits

SendGrid has rate limits based on your plan. For bulk sending, consider:

```typescript
// Use batch endpoints for large numbers of contacts
// Or spread sends over time
```

## Error Handling

```typescript
import { RestApiValidationError } from "@superblocksteam/sdk-api";

try {
  await ctx.integrations.sendgrid.apiRequest(
    { method: "POST", path: "/v3/mail/send", body: { ... } },
    { response: SendEmailResponseSchema }
  );
} catch (error) {
  if (error instanceof RestApiValidationError) {
    console.error("Validation failed:", error.details.zodError);
  }
}
```

## API Reference

- [SendGrid API Documentation](https://docs.sendgrid.com/api-reference)
- [Mail Send](https://docs.sendgrid.com/api-reference/mail-send/mail-send)
- [Marketing Contacts](https://docs.sendgrid.com/api-reference/contacts/add-or-update-a-contact)
- [Dynamic Templates](https://docs.sendgrid.com/ui/sending-email/how-to-send-an-email-with-dynamic-templates)
