Code Monkey home page Code Monkey logo

courier-node's Introduction

Courier: Your Complete Communication Stack

npm shield fern shield

This is the official node.js module for sending notifications with node.js with the Courier REST API.

Courier docs โ€ข 3 Different Ways To Send Emails With Node.js

Installation (via npm)

npm install @trycourier/courier

Requirements

You will need to get a Courier API key to get started. You can sign up and create one for free at courier.com.

Upgrade Guides

v5 to v6

v6 of our SDK is automatically generated by Fern. v6 comes with several improvements that we describe below:

  • Resource-scoped SDK methods: Endpoints are scoped under their resource. For example, instead of courier.deleteBrands the SDK now reads courier.brands.delete(...)
  • Docs on Hover: All endpoint and parameter level documentation that you see on our docs website is now embedded directly within the SDKs.
  • Retries with exponential backoff: The SDK will automatically retry failures with exponential backoff. You can configure the retries by setting maxRetries.
    const response = courier.send(..., {
      maxReries: 0 // set to 0 if you want to disable retries
    })
  • Configurable Timeouts: The SDK defaults to a 60 second timeout. You can also configure this value per-request.
    const response = courier.send(..., {
      timeoutInSeconds: 45 // set to 45 seconds for this particular request
    })
  • Support for multiple runtimes: The SDK uses global fetch when available, otherwise defaults to node-fetch. This means you can use Courier in multiple runtimes; Node.js, Vercel, and Cloudflare Workers.

v3 to v4

v4 uses native fetch client to make requests or falls back to a polyfill if the client doesn't exist in the environment it's running in. Check Error Handling out.

Getting Started

import { CourierClient } from "@trycourier/courier";

const courier = new CourierClient({ authorizationToken: "<AUTH_TOKEN>" }); // get from the Courier UI

// Example: send a basic message to an email recipient
const { requestId } = await courier.send({
  message: {
    to: {
      data: {
        name: "Marty",
      },
      email: "[email protected]",
    },
    content: {
      title: "Back to the Future",
      body: "Oh my {{name}}, we need 1.21 Gigawatts!",
    },
    routing: {
      method: "single",
      channels: ["email"],
    },
  },
});

// Example: send a basic message to an sms recipient
const { requestId } = await courier.send({
  message: {
    to: {
      data: {
        name: "Jenny",
      },
      phone_number: "8675309",
    },
    content: {
      title: "Back to the Future",
      body: "Oh my {{name}}, we need 1.21 Gigawatts!",
    },
    routing: {
      method: "single",
      channels: ["sms"],
    },
  },
});

// Example: send a message to various recipients
const { requestId } = await courier.send({
  message: {
    to: [
      {
        user_id: "<USER_ID>", // usually your system's User ID associated to a Courier profile
        email: "[email protected]",
        data: {
          name: "some user's name",
        },
      },
      {
        email: "[email protected]",
        data: {
          name: "Marty",
        },
      },
      {
        email: "[email protected]",
        data: {
          name: "Doc",
        },
      },
      {
        phone_number: "8675309",
        data: {
          name: "Jenny",
        },
      },
    ],
    content: {
      title: "Back to the Future",
      body: "Oh my {{name}}, we need 1.21 Gigawatts!",
    },
    routing: {
      method: "all",
      channels: ["sms", "email"],
    },
  },
});

// Example: send a message supporting email & SMS
const { requestId } = await courier.send({
  message: {
    template: "<TEMPLATE_OR_EVENT_ID>", // get from the Courier UI
    to: {
      user_Id: "<USER_ID>", // usually your system's User ID
      email: "[email protected]",
      phone_number: "555-228-3890",
    },
    data: {}, // optional variables for merging into templates
  },
});

// Example: send a message to a list
const { requestId } = await courier.send({
  message: {
    template: "<TEMPLATE_OR_EVENT_ID>", // get from the Courier UI
    to: {
      list_id: "<LIST_ID>", // e.g. your Courier List Id
    },
    data: {}, // optional variables for merging into templates
  },
});

// Example: send a message to a pattern
const { requestId } = await courier.send({
  message: {
    template: "<TEMPLATE_OR_EVENT_ID>", // get from the Courier UI
    to: {
      list_pattern: "<PATTERN>", // e.g. example.list.*
    },
    data: {}, // optional variables for merging into templates
  },
});

// Example: send a message to a list, pattern and user
const { requestId } = await courier.send({
  message: {
    content: {
      title: "Hello",
      body: "Good morning!"
    },
    to: [
      {
        list_pattern: "<PATTERN>", // e.g. example.list.*
      },
      {
        list_id: "<LIST_ID>", // e.g. your Courier List Id
      },
      {
        email: "[email protected]"
      }
    ]
  },
  routing: {
    method: "single",
    channels: ["email"],
  },
});

// Example: send a basic message that expires after the specified timeout
const { requestId } = await courier.send({
  message: {
    to: {
      data: {
        name: "Marty",
      },
      email: "[email protected]",
    },
    content: {
      title: "Back to the Future",
      body: "Oh my {{name}}, we need 1.21 Gigawatts!",
    },
    routing: {
      method: "single",
      channels: ["email"],
    },
    timeout: {
      message: 3600000 // 1 hour in milliseconds
    },
  },
});

// Example: send a basic message with a trace id
const { requestId } = await courier.send({
  message: {
    to: {
      data: {
        name: "Marty",
      },
      email: "[email protected]",
    },
    content: {
      title: "Back to the Future",
      body: "Oh my {{name}}, we need 1.21 Gigawatts!",
    },
    routing: {
      method: "single",
      channels: ["email"],
    },
    metadata: {
      trace_id: "ravenclaw-for-the-win"
    },
  },
});

Environment Variables

courier-node supports credential storage in environment variables. If no authorizationToken is provided when instantiating the Courier client (e.g., const courier = CourierClient();), the value in the COURIER_AUTH_TOKEN env var will be used.

If you need to use a base url other than the default https://api.courier.com, you can set it using the COURIER_BASE_URL env var.

Advanced Usage

const { CourierClient } = require("@trycourier/courier");

const courier = CourierClient({ authorizationToken: "<AUTH_TOKEN>" });

async function run() {
  // Example: send a message
  const { requestId } = await courier.send({
    message: {
      template: "<TEMPLATE_OR_EVENT_ID>",
      to: {
        // optional
        user_id: "<RECIPIENT_ID>",
      },
      data: {}, // optional
      brand_id: "<BRAND_ID>", //optional
      channels: {}, // optional
      providers: {}, // optional
    },
  });
  console.log(requestId);

  // Example: send message with utm metadata
  const { requestId } = await courier.send({
    message: {
      template: "<TEMPLATE_OR_EVENT_ID>",
      to: {...},
      routing: {
        method: "single",
        channels: ["email"],
      },
      channels: {
        email: {
          routing_method: "all",
          providers: ["sendgrid", "sns"],
          metadata: {
            utm: {
              medium: "f",
              campaign: "g",
            },
          },
        },
      },
      providers: {
        sns: {
          metadata: {
            utm: {
              medium: "h",
            },
          },
        },
      }, // optional
      metadata: {
        utm: {
          source: "a",
          medium: "b",
          campaign: "c",
        },
      },
      timeout: {
        message: 300000,
        channel: {
          email: 1000 // 1 second
        }
      }
    },
  });

/**
 * If the template or content contains any action blocks, the hyperlinks will be augmented with utm compliant query parameters.
 *
 * The resulting link of an action block sent through sendgrid would be:
 * www.example.com?utm_source=a&utm_medium=f&utm_campaign=g
 *
 * While the resulting link of an action block sent through sns would be:
 * www.example.com?utm_source=a&utm_medium=h&utm_campaign=g
 *
 * Notice that provider metadata supersedes channel metadata and channel metadata supersedes message metadata
 *
 **/

/**
 * If the message includes a timeout property we will start timing out messages after the first attempt.
 * We are able to timeout complete channels or specific providers.
 **/

  // Example: get a message status
  const messageStatus = await courier.messages.get(requestId);
  console.log(messageStatus);

  // Example: get a message history
  const { results } = await courier.messages.getHistory(requestId);
  console.log(results);

  // Example: get a message output
  const { results } = await courier.messages.getContent(requestId);
  console.log(results);

  // Example: get all messages
  const { paging, results } = await courier.messages.list();
  console.log(results);

  // Example: replace a recipient's profile
  const { status: replaceStatus } = await courier.profiles.replace(
    "<RECIPIENT_ID>",
    {
      profile: {
        email: "[email protected]",
      },
    }
  );
  console.log(replaceStatus);

  // Example: merge into a recipient's profile
  const { status: mergeStatus } = await courier.profiles.create(
    "<RECIPIENT_ID>",
    {
      profile: {
        sms: "555-555-5555",
      },
    }
  );
  console.log(mergeStatus);

  // Example: get a recipient's profile
  const { profile } = await courier.profiles.get("<RECIPIENT_ID>");
  console.log(profile);

  // Example: get all brands
  const { paging, results } = await courier.brands.list({
    cursor: "<CURSOR>", // optional
  });
  console.log(results);

  // Example: get a specific brand
  const brand = await courier.brands.get("<BRAND_ID>");
  console.log(brand);

  // Example: create a brand
  const newBrand = await courier.brands.create({
    name: "My Brand",
    settings: {
      colors: {
        primary: "#0000FF",
        secondary: "#FF0000",
        tertiary: "#00FF00",
      },
    },
  });
  console.log(newBrand);

  // Example: replace a brand
  const replacedBrand = await courier.brands.replace("<BRAND_ID>", {
    name: "My New Brand",
    settings: {
      colors: {
        primary: "#FF0000",
        secondary: "#00FF00",
        tertiary: "#0000FF",
      },
    },
  });
  console.log(replacedBrand);

  // Example: delete a brand
  await courier.brands.delete("<BRAND_ID>");

  // Example: get all lists
  const { paging, items } = await courier.lists.list({
    cursor: "<CURSOR>", // optional
  });
  console.log(items);

  // Example: get a specific list
  const list = await courier.lists.get("<LIST_ID>");
  console.log(list);

  // Example: create or replace a list
  const replacedList = await courier.lists.update("<LIST_ID>", {
    name: "My New List",
  });
  console.log(replacedList);

  // Example: delete a list
  await courier.lists.delete("<LIST_ID>");

  // Example: restore a list
  await courier.lists.restore("<LIST_ID>");

  // Example: get a list's subscribers
  const { paging, items } = await courier.lists.getSubscribers("<LIST_ID>");
  console.log(items);

  // Example: replace many recipients to a new or existing list
  await courier.lists.updateSubscribers("<LIST_ID>", [
    { recipientId: "RECIPIENT_ID_1" },
    { recipientId: "RECIPIENT_ID_2" },
  ]);

  // Example: subscribe single recipient to a new or existing list
  await courier.lists.subscribe("<LIST_ID>", "<RECIPIENT_ID>");
  console.log(recipient);

  // Example: unsubscribe recipient from list
  await courier.lists.unsubscribe("<LIST_ID>", "<RECIPIENT_ID>");

  // Example: get a recipient's subscribed lists
  const { paging, items } = await courier.lists.getSubscribers(
    "<RECIPIENT_ID>"
  );
  console.log(items);

  // Example: Automation Ad-Hoc Invoke
  const { runId } = await courier.automations.invokeAdHocAutomation({
    automation: {
      cancelation_token: "I_AM_TOKEN",
      steps: [
        {
          action: "send",
        },
      ],
    },
    brand: "BRAND_ID",
    data: {
      example: "EXAMPLE_DATA",
    },
    profile: {
      email: "[email protected]",
    },
    recipient: "RECIPIENT_ID",
    template: "TEMPLATE_NAME_OR_ID",
  });
  console.log(runId);

  // Example: Automation Invoke Template
  const { runId } = await courier.automations.invokeAutomationTemplate(
    "AUTOMATION_TEMPLATE_ID",
    {
      brand: "BRAND_ID",
      data: {
        example: "EXAMPLE_DATA",
      },
      profile: {
        email: "[email protected]",
      },
      recipient: "RECIPIENT_ID",
      template: "TEMPLATE_NAME_OR_ID",
    }
  );
  console.log(runId);

  // Example: List notifications
  const { paging, results } = await courier.notifications.list();
  console.log(results);

  // Example: Get notification content
  const { blocks, channels } = await courier.notifications.getContent(
    "notification1"
  );
  console.log(blocks);
  console.log(channels);

  // Example: Get notification draft content
  const { blocks, channels } = await courier.notifications.getDraftContent(
    "notification1"
  );
  console.log(blocks);
  console.log(channels);

  // Example: Post notification variations
  await courier.notifications.updateVariations("notification1", {
    blocks: [
      {
        id: "block_1d4c32e0-bca8-43f6-b5d5-8c043199bce6",
        type: "text",
        locales: {
          fr_FR: "block fr 1",
        },
      },
      {
        id: "block_6d50a6e3-ecc3-4815-bf51-0202c6bf54e2",
        type: "text",
        locales: {
          fr_FR: "block fr 2",
        },
      },
    ],
    channels: [
      {
        id: "channel_1ba46024-f156-4ed7-893b-cb1cdcfbd36e",
        type: "email",
        locales: {
          fr_FR: {
            subject: "French Subject",
          },
        },
      },
      {
        id: "channel_2c2aad1c-30f0-4a55-8d8f-d213f32147bc",
        type: "push",
        locales: {
          fr_FR: {
            title: "French Title",
          },
        },
      },
    ],
  });

  // Example: Post notification draft variations
  await courier.notifications.updateDraftVariations("notification1", {
    blocks: [
      {
        id: "block_1d4c32e0-bca8-43f6-b5d5-8c043199bce6",
        type: "text",
        locales: {
          fr_FR: "block fr 1",
        },
      },
      {
        id: "block_6d50a6e3-ecc3-4815-bf51-0202c6bf54e2",
        type: "text",
        locales: {
          fr_FR: "block fr 2",
        },
      },
    ],
    channels: [
      {
        id: "channel_1ba46024-f156-4ed7-893b-cb1cdcfbd36e",
        type: "email",
        locales: {
          fr_FR: {
            subject: "French Subject",
          },
        },
      },
      {
        id: "channel_2c2aad1c-30f0-4a55-8d8f-d213f32147bc",
        type: "push",
        locales: {
          fr_FR: {
            title: "French Title",
          },
        },
      },
    ],
  });

  // Example: Get notification submission checks
  const { checks } = await courier.notifications.getSubmissionChecks(
    "notification1",
    "submission1"
  );
  console.log(checks);

  // Example: Put notification submission checks
  const { checks } = await courier.notifications.replaceSubmissionChecks(
    "notification1",
    "submission1",
    {
      checks: [
        {
          id: "check1",
          status: "RESOLVED",
          type: "custom",
        },
      ],
    }
  );
  console.log(checks);

  // Example: Cancel notification submission
  await courier.notifications.cancelSubmission("notification1", "submission1");

  // Bulk Processing
  // Example: create a job (API v1 semantics)
  const response = await courier.bulk.createJob({
    message: {
      event: "RR4NDQ7NZ24A8TKPWVBEDGE15E9A",
    },
  });
  console.log(response);

  // Example: create a job (API v2 semantics)
  const response = await courier.bulk.createJob({
    message: {
      message: {
        template: "RR4NDQ7NZ24A8TKPWVBEDGE15E9A",
      },
    },
  });
  console.log(response);

  // Example: get a job
  const response = await courier.bulk.getJob({
    jobId: "1-61efe386-6ff57552409e311b7a1f371f",
  });
  console.log(response);

  // Example: Ingest users in a job (API v1 semantics)
  const response = await courier.bulk.ingestUsers({
    jobId: "1-61efe386-6ff57552409e311b7a1f371f",
    users: [
      {
        profile: {
          email: "[email protected]",
        },
      },
    ],
  });
  console.log(response);

  // Example: Ingest users in a job (API v2 semantics)
  const response = await courier.bulk.ingestUsers({
    jobId: "1-61efe386-6ff57552409e311b7a1f371f",
    users: [
      {
        to: {
          email: "[email protected]",
        },
      },
    ],
  });
  console.log(response);

  // Example: Run a job
  await courier.bulk.runJob("1-61efe386-6ff57552409e311b7a1f371f");

  // Example: Get user details in a job
  const response = await courier.bulk.getUsers(
    "1-61efe386-6ff57552409e311b7a1f371f"
  );
  console.log(response);
}

run();

Error Handling

This package tries to use the native fetch client to make requests or falls back to a polyfill if the client doesn't exist in the environment it's running in.

All network related promise rejections are not handled in any way. All successfully made requests that produce errors on the server side are resulting in promise rejections with custom CourierError error type.

CourierError extends native Error interface with two extra properties:

  • statusCode: this is the status code of the response
  • body: this is the body of the response
// Error handling example
import { CourierError, CourierClient } from "@trycourier/courier";

const courier = new CourierClient();

try {
  await courier.send(/* ... */);
} catch (error) {
  if (error instanceof CourierError) {
    console.log("Failed to send with status code:", error.statusCode);
    console.log("The Courier body is:", error.body);
    console.log("The error message is:", error.message);
  } else {
    console.log(
      "There was a problem making that request. Make sure you are online."
    );
  }
}

Idempotency

For POST methods, you can supply an idempotencyKey in the config parameter to ensure the idempotency of the API Call. We recommend that you use a V4 UUID for the key. Keys are eligible to be removed from the system after they're at least 24 hours old, and a new request is generated if a key is reused after the original has been removed. For more info, see Idempotent Requests in the Courier documentation.

import { CourierClient } from "@trycourier/courier";
import uuid4 from "uuid4";

const courier = new CourierClient();
const idempotencyKey = uuid4();

async function run() {
  const { requestId } = await courier.send(
    {
      template: "<TEMPLATE_OR_EVENT_ID>",
      to: {
        user_id: "<USER_ID>",
        email: "[email protected]",
        phone_number: "555-867-5309",
      },
      data: {
        world: "JavaScript!",
      },
    },
    {
      idempotencyKey,
    }
  );
  console.log(requestId);
}

run();

Audiences

Audiences APIs are used to create, get, update, and delete audiences. A Courier Audience is a dynamic group of users (created using Courier's Profile API) that matches a set of criteria. Audience is reactive to changes in the user's profile. As you change user profile using profiles API, the audience will be updated accordingly. You will not have to maintain a list of users in your audience. Courier takes care of that for you. If you have potentially large set of users, you first create an audience and then use the audience's id to retrieve the list of users. Once you satified with the calculated list of users, you can use the audienceId to send notification using send API.

// Example: create audience which would allow sending notification to all users that match the given filter criteria
const { audienceId } = await courier.audiences.put({
  id: "<AUDIENCE_ID>",
  filter: {
    operator: "EQ",
    path: "title",
    value: "Software Engineer",
  },
});

// To retrieve list of members in a given audience, you can use the following:
const { items: audienceMembers } = await courier.audiences.listMembers(
  audienceId
  cursor: "<CURSOR>", // optional
);

// To send a notification to all users that match the given filter criteria, you can use the following:
const { requestId } = await courier.send({
  message: {
    template: "<TEMPLATE_OR_EVENT_ID>", // This can be inline content as well
    to: {
      audience_id: audienceId,
    },
    data: {}, // optional
    brand_id: "<BRAND_ID>", //optional
    routing: {},
    channels: {}, // optional
    providers: {}, // optional
  },
});

Tenants

The Tenants API is designed to enable multi-tenant notification workflows. This is useful for defining user to tenant level relationships, especially in the context of B2B applications.

Use Cases:

  • Sending branded notifications on behalf of an organization
  • Creating slack-bots on behalf of an organization

Creating a Tenant

const { id } = await courier.tenants.createOrReplace("<TENANT_ID>", {
  name: "Courier",
  user_profile: {
    slack: {
      access_token: "<SLACK_ACCESS_TOKEN_SCOPED_TO_THE_TENANT>",
    },
  },
});

Retrieving a Tenant

const tenant = await courier.tenants.get("<TENANT_ID>");

Deleting a Tenant

await courier.tenants.delete("<TENANT_ID>");

Listing Tenants

const { items: tenants, has_more } = await courier.tenants.list();

Updating user preferences

Courier currently does not allow creating new topics via the API. You must create topics via the Courier UI. Once a topic is created, you can update a user's preferences for that topic via the API.

  await courier.users.preferences.update("<USER_ID>", "<VALID_TOPIC_ID>", {
    default_status: "OPTED_IN",
    status: "OPTED_OUT",
  });

Getting user preferences

  • Get all topic level preferences for a user
  const { items: userPreferences } = await courier.users.preferences.list(
    "<USER_ID>"
  );
  • Get a specific topic level preference for a user
  const userPreference = await courier.users.preferences.get(
    "<USER_ID>",
    "<VALID_TOPIC_ID>"
  );

Contributing

While we value open-source contributions to this SDK, this library is generated programmatically. Additions made directly to this library would have to be moved over to our generation code, otherwise they would be overwritten upon the next generated release. Feel free to open a PR as a proof of concept, but know that we will not be able to merge it as-is. We suggest opening an issue first to discuss with us!

On the other hand, contributions to the README are always very welcome!

License

MIT License

Author

Courier ([email protected])

courier-node's People

Contributors

aydrian avatar cgradwohl avatar dependabot[bot] avatar drew-y avatar fern-api[bot] avatar helenamerk avatar jasonbaxtell avatar joshshowalter avatar jrweingart avatar lednax avatar lkazberova avatar michalbiesek avatar mikemilla avatar rileylnapier avatar scarney81 avatar schlosser avatar suhasdeshpande avatar sw4ti493 avatar tk26 avatar troygoode avatar vahnag avatar vatasha avatar worldsoup avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

courier-node's Issues

This is more of an improvement, is it possible to insert hyperlinks ( just like anchor tag in html ) in the message body to redirect to a url.

Expected Behavior

I tried to use direct html code, and read the documentation examples, but couldn't find anything like that. Maybe you could help out.

Actual Behavior

Not able to embed links in message body.

Steps to Reproduce the Problem

Example Code :
return courier.send({
message: {
content: {
title: "Password Reset",
body:
We received a request to change password for this account.Click this <link> to reset your password.
},
to: {
email:
}
}
});

Specifications

  • Version:
  • Platform:
  • Subsystem:

Support types for the "new" send step in the invoke automations API

Expected Behavior

According to the docs, the "new" send step in Automations accepts two properties, "action" and "message". So an attempt to use the "message" property should not throw a typescript error.

Actual Behavior

A typescript error is seen.
Object literal may only specify known properties, and 'message' does not exist in type 'IAutomationSendStep'
Call completes SUCCESSFULLY, and the automation + email send are successful

image

code sample

import { CourierClient } from "@trycourier/courier";

const courier = CourierClient({
  authorizationToken: "pk_test_XXXXXXX",
});

const doAutomation = async () => {
  const { runId } = await courier.automations.invokeAdHocAutomation({
    automation: {
      cancelation_token: "I_AM_TOKEN",
      steps: [
        {
          action: "send",
          message: {
            to: {
              data: {
                name: "Manu Rana",
                user_id: "123",
              },
              email: "[email protected]",
            },

            content: {
              title: "Back to the Future II",
              body: "Oh my {{name}}, we need 1.21 Gigawatts!",
            },

            routing: {
              method: "single",
              channels: ["email"],
            },
          },
        },
      ],
    },
  });
  console.log(runId);
};

doAutomation();

Steps to Reproduce the Problem

  1. Create a simple automation with one step using typescript API
  2. Notice the type errors while compiling

Specifications

  • Version: 3.13.1
  • Platform: Node 16.14.0
  • Subsystem: Typescript 4.3.5

Please can idempotency be published

PR #16 was merged over a month ago. I'm looking to use the idempotency configuration but these changes haven't been published to npm yet.

Please can it be published? ๐Ÿ™

courier.send returns empty response

Expected Behavior

We want to track sent message status.
For that we use requestId from courier.send method, as an argument in courier.getMessage.
We expect courier.send response to contain requestId according to courier docs

Actual Behavior

courier.send response is empty
courier.getMessage is sent with undefined and fails with status 400.
The email notifications are delivered, but we receive issues in Sentry:

Screenshot 2022-08-09 at 16 36 57

Steps to Reproduce the Problem

Here is the code we use to send the emails.

 const message = {
    template: COURIER_EVENT_ID,
    to: {
      email,
    },
    data: {
      value,
      imageUrl,
    },
  };

  const sendResponse = await courier.send(
    { message },
    { idempotencyKey: `${COURIER_EVENT_ID}:${idempotencyKey}` }
  );

  console.log('sale notification send response', JSON.stringify(sendResponse));

  const { requestId } = sendResponse;

  const { status } = await courier.getMessage(requestId);

  if (status === 'UNDELIVERABLE') {
    console.warn('Sale notification failed, check Courier');
  }

Are we doing something wrong?
Is there any other way to get notified when the email sending has failed?

Specifications

"node": "^16.4.0"
"@trycourier/courier": "^3.14.1"
"next": "12.2.2"
"react": "18.2.0",

Property `slack` is missing in `MessageRecipient`

Say I want to send a message to Slack. This is the format I found at https://www.courier.com/docs/platform/channels/direct-message/slack/#sending-to-a-public-or-private-channel.

import { CourierClient } from "@trycourier/courier"

const courier = new CourierClient({ authorizationToken: "courier_auth_token" })

await courier.send({
	message: {
		to: {
			slack: { access_token: "slack_access_token", channel: "channel_id" },
		},
		data: { message: "this is a message" },
	},
})

The project doesn't pass sanity checks because MessageRecipient doesn't contain slack

src/courier/slack.ts:8:4 - error TS2353: Object literal may only specify known properties, and 'slack' does not exist in type 'MessageRecipient'.

8    slack: { access_token: "slack_access_token", channel: "channel_id" },
     ~~~~~

  node_modules/@trycourier/courier/api/resources/send/types/ContentMessage.d.ts:16:5
    16     to: Courier.MessageRecipient;
           ~~
    The expected type comes from property 'to' which is declared here on type 'Message'


Found 1 error in src/courier/slack.ts:8

Feature request: REST endpoint for counting member of audience and number of subscribers

Hi Courier team,
I am writing to request your assistance in implementing an endpoint that can count the total number of members in our audience and list.
Currently, I have the use case to count the total number of members of specific audience and total number of subscribers with multiple calls for https://www.courier.com/docs/reference/audiences/list-audience-members/ and similar for the lists: https://www.courier.com/docs/reference/lists/subscriptions/.
I think this additional endpoints would be useful for Courier as well since it would limit the traffic to your servers.

Support for Cloudflare Workers

Expected Behavior

Builds successfully.

Actual Behavior

Fails.

Steps to Reproduce the Problem

Install on an application running on Cloudflare Workers.

This library fails as it relies on XHR/Axios under the hood, which Cloudflare Workers don't support. My suggestion would be to change requests to use fetch, and provide a configuration option when initialising to supply your own instance of fetch. This could then be used with cross-fetch to support pure Node environments and the 'webworker-ish' environment of CF workers with the same codebase.

This isn't preventing me using it (just wrote my own API wrapper), but it would be nice to have.

Request: support idempotencyKey in automations client

Right now, you can specify an idempotencyKey in the send module via

courier.send({...}, {idempotencyKey: 'blah'});

It would be great if you could add this to the automations client module as well, so I can do something like

courier.automations.invokeAdHocAutomation({...}, {idempotencyKey: 'blah'});

Support /users/:user_id/tokens

What's the best way to store push notification (FCM) tokens using the /users/:user_id/tokens/:token API?

I could make the raw API call myself but having both the SDK and raw API calls is a bit meh.

Does the SDK expose any kind of raw HTTP call function which auto attaches the bearer etc?

Example in repo no longer working since v5.0

Expected Behavior

The following example from the readme should work:

courier-node/README.md

Lines 831 to 834 in 041a204

await courier.users.put("<USER_ID>", {
accounts: [{ account_id: "ACCOUNT_ID", profile: { foo: "bar" } }],
profile: { name: "John Doe" },
});

Actual Behavior

Receiving 400 error:

 {
    message: 'must NOT have additional properties',
    type: 'invalid_request_error'
  }

Steps to Reproduce the Problem

  1. Make request as per readme
  2. Catch thrown response
  3. Inspect response

Specifications

  • Version: 5.0
  • Platform: node18
  • Subsystem: macos

[Feature request] Fetch all courier profiles

Hi Courier Team!
I would like to ask you to introduce a method or rest endpoint for fetching all courier profiles.
Currently, it takes too long to fetch profile after profile. Moreover, DynamoDB does not scale as the docs states, and there are internal server errors after some time, for example:
image
image

Courier 6.0.0 not published to npm

I am unable to install Courier from npm after the latest version was released

npm ERR! 404 Not Found - GET https://registry.npmjs.org/@trycourier%2fcore - Not found

npm ERR! 404  '@trycourier/core@^6.0.0' is not in this registry.

It also appears that trying to install older versions are looking for the latest package version (6.0.0) which means we are unable to install previous versions

Type signature not matching server expected response

Expected Behavior

Typescipt doesn't complain for the correct server payload

Actual Behavior

The example in the repo show passing in an object with an accounts property:

courier-node/README.md

Lines 837 to 843 in 041a204

#### Updating user accounts
```ts
await courier.users.putAccounts("<USER_ID>", {
accounts: [{ account_id: "ACCOUNT_ID", profile: { foo: "bar" } }],
});
```

However, this creates a type error:

Argument of type '{ accounts: { account_id: string; profile: {}; }[]; }' is not assignable to parameter of type 'IUserAccount[]'.
  Object literal may only specify known properties, and 'accounts' does not exist in type 'IUserAccount[]'.

But this response actually works.

If we keep typescript happy with:

await courier.users.putAccounts("<USER_ID>",  [
  { account_id: "ACCOUNT_ID", profile: { foo: "bar" } }
]);

We get a 400 error with: { message: 'must be object', type: 'invalid_request_error' }

Steps to Reproduce the Problem

  1. Follow the docs example
  2. Observe the type error
  3. Make typescript happy
  4. Observe the 400 server error

Specifications

  • Version: 5.0
  • Platform: node18
  • Subsystem: macos

Need courier.postProfile() method need

Expected Behavior

Actual Behavior

Steps to Reproduce the Problem

Need method,
import { CourierClient } from "@trycourier/courier";

const courier = CourierClient({ authorizationToken: "<AUTH_TOKEN>" });
courier.postProfile()

Specifications

  • Version:
  • Platform:
  • Subsystem:

Use a `fetch` obtained from the constructor

I want to use a custom fetch implementation to add some HTTP-specific handling / logging / etc.

import { CourierClient } from "@trycourier/courier"
import { ssrFetch } from "./ssr_fetch.ts"

const courier = new CourierClient({
	authorizationToken: "<AUTH_TOKEN>",
	fetch: ssrFetch
})

Is it possible to modify the constructor so we can pass a custom fetch?

Similar to LemmyNet/lemmy-js-client#132

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.