Создание менеджера рассылок с Turso и Qwik

Платформы для рассылки новостей ненадежны или дороги. Я создал собственное решение, используя Qwik и Turso, пограничную базу данных на базе SQLite.

Информационный бюллетень — это инструмент стратегии маркетинга по электронной почте, используемый отдельными лицами, издателями, организациями и предприятиями для обмена ценной информацией и интересным контентом со своими клиентами, помогая в продвижении продаж и привлечении трафика на свои веб-сайты.

В этом посте я собираюсь продемонстрировать, как создать информационный бюллетень, используя Turso, пограничную базу данных, основанную на libSQL от ChiselStrike, и Qwik, возобновляемую среду мгновенного запуска приложений, созданную людьми из Builder. .ио.

Прежде чем перейти к строительной части, я хотел бы поделиться несколькими причинами, почему я работаю над этим и использую выбранную технологию.

Платформы для рассылок либо ненадежны, либо дороги, вот почему я развернул свою собственную

Мой любимый вариант для моих собственных информационных бюллетеней был Revue, но Twitter недавно закрыл его, а другие, такие как Ghost и ConvertKit, на мой взгляд, довольно дороги, поэтому я провел небольшое исследование, чтобы создать свой собственный, чтобы у вас не было к.

Я следил за Qwik, возобновляемой, мгновенно запускаемой средой приложений, и мне нравится философия, стоящая за ней, поэтому было несложно ее построить. Я соединил его с Turso, DBaaS (база данных как услуга), которая имеет лучший DX (опыт разработчика), с которым я когда-либо сталкивался — что является одной из причин, по которой я присоединился к компании, полное раскрытие — для скорость и низкую задержку, которую он может обеспечить вместе с Qwik.

Создание менеджера рассылок с помощью Turso и Qwik

Создаваемый нами менеджер рассылки состоит из трех страниц.

Первая страница — это та, с которой подписчики взаимодействуют при подписке, она включает краткое введение в суть информационного бюллетеня и форму.

Во-вторых, страница, доступная редактору информационного бюллетеня, которая позволяет им просматривать электронные письма подписчиков, блоги, на которые они подписаны (если их несколько), и даты подписки.

Третья страница — это страница отписки. Как и в любом хорошем информационном бюллетене, вы хотели бы предоставить своим подписчикам возможность как подписаться на рассылку, так и отказаться от нее.

В идеале вторая страница должна включать некоторую аутентификацию, поскольку она должна гарантировать ограниченный доступ, но мы не будем рассматривать аутентификацию в этом посте.

Исходный код проекта, над которым мы работаем в этом посте, также доступен в этом репозитории GitHub.

Предпосылки:

Настройка базы данных Turso

Инструкции по установке Turso, созданию базы данных и получению URL-адреса базы данных Turso можно найти в инструкциях по инициализации проекта в репозитории GitHub.

Вам понадобится URL-адрес базы данных Turso, полученный после создания базы данных, поскольку он необходим внутри нашего приложения.

Если вы не можете получить доступ к Turso через частную бета-программу, вы все равно можете продолжить настройку проекта, используя маршрут локальной базы данных SQLite. Детали настройки локальных баз данных SQLite задокументированы здесь.

Приложение Qwik

Как уже говорилось, и как вы заметите, изучив исходный код, проект построен с использованием фреймворка Qwik, и мы используем клиент libSQL для TypeScript (@libsql/client) для выполнения запросов на нашем база данных. Благодаря этому мы можем беспрепятственно работать с локальными файловыми базами данных SQLite так же, как с нашими базами данных Turso на периферии.

То, что мы будем неоднократно видеть в коде Qwik, — это символ $. Пусть вас не смущает, Qwik использует его для обозначения извлечения кода как для разработчика, так и для оптимизатора Qwik. Qwik разбивает приложение на множество более мелких частей, называемых символами, с помощью Qwik Optimizer. Подробнее об этом можно прочитать в документации Qwik.

Давайте рассмотрим самые интересные части менеджера рассылок.

Домашняя страница информационного бюллетеня

Внутри домашней страницы мы обновляем состояние загрузки и вызываем функцию subscribeToNewsletter() при отправке формы <form onSubmit$>.

useSignal() и useStore() — это два хука Qwik, которые вы найдете на всех страницах, используемых для объявления реактивного состояния. useSignal() принимает начальное значение и возвращает реактивный сигнал, состоящий из объекта с одним свойством .value, в то время как хук useStore() принимает объект в качестве начального значения и возвращает реактивный объект.

Серверная функция в Qwik — server$ позволяет нам работать на серверном уровне приложения рядом с нашим клиентским кодом. Вот больше информации об этом и вот демонстрация.

subscribeToNewsletter() внутри домашней страницы — это функция сервера, которая отправляет информацию о подписке в базу данных Turso и информирует нас о состоянии подписки.

import { createClient } from "@libsql/client";

export const subscribeToNewsletter = server$(
  async (email: string, newsletter: string) => {
    if (!email || !newsletter) {
      return {
        success: false,
        message: "Email cannot be empty!",
      };
    }

    const db = createClient({
      url: import.meta.env.VITE_DB_URL,
    });

    await db.execute("insert into newsletters(email, website) values(?, ?)", [
      email,
      newsletter,
    ]);

    const response = await db.execute(
      "select * from newsletters where email = ? and website = ?",
      [email, newsletterBlog]
    );

    const subscriber = responseDataAdapter(response);

    return subscriber[0]
      ? {
          success: true,
          message: "You've been subscribed!",
        }
      : {
          success: false,
          message: "Sorry something isn't right, please retry!",
        };
  }
);

export default component$(() => {
  const email = useSignal("");
  const loading = useSignal(false);
  const emailRegex = /\b[\w.-]+@[\w.-]+\.\w{2,4}\b/gi;
  const notification = useStore({
    message: "",
    status: "",
  });

  return (
      <form
        preventdefault:submit onSubmit$={async () => {
          if (!emailRegex.test(email.value)) {
            alert("Email not valid!");
            return;
          }
          loading.value = true;
          const response = await subscribeToNewsletter(
            email.value,
            newsletterBlog
          );
          loading.value = false;
          notification.message = response.message;
          notification.status = response.success ? "success" : "error";
        }}
      >
        <input
          onInput$={(e) => {
            email.value = (e.target as HTMLInputElement).value
          }}
          name="email"
          type="email"
        />
        <button
          type="submit"
        >Subscribe</button>
      </form>
  );
});

Qwik также поддерживает SEO (поисковую оптимизацию). Чтобы настроить его на страницах, мы экспортируем головной объект типа DocumentHead с метаданными заголовка HTML. Мы можем видеть это внутри каждой из трех страниц: домашней, админки и отписки.

Вот предварительный просмотр главной страницы.

Страница администратора рассылки

В пользовательском интерфейсе компонента страницы администрирования новостной рассылки мы используем компонент Ресурс Qwik, который отображает свои дочерние элементы, когда переданный ресурс разрешается, и отображает запасной вариант, когда он находится в ожидании или отклонен.

В Qwik ресурсы создаются методом useResource$. Мы можем увидеть демонстрацию ресурса, взглянув на subscribersResource, необходимый для <Resource ... />component, упомянутого выше.

const subscriberRows = (subscribers: NewsletterSubscriber[]) => {
  return subscribers?.length < 1 ? (
    <tr>
      <td
        colSpan={4}
      >
        No subscribers found
      </td>
    </tr>
  ) : (
    subscribers?.map((sub: NewsletterSubscriber, index: number) => {
      return (
        <tr key={sub.id}>
          <td>{index + 1}</td>
          <td>{sub.email}</td>
          <td>
            {sub.website}
          </td>
          <td>
            {formatDate(sub.created_at)}
          </td>
        </tr>
      );
    })
  );
};

export default component$(() => {

  const subscribersResource = useResource$<ResourceResponse>(async () => {
    const response = await db.execute('select * from newsletters');
    const subscribers = response?.success ? responseDataAdapter(response) : [];

    return {
      message: "Fetched subscribers",
      data: subscribers,
    };
  });

  return(
    <div>
      <h1>Newsletter Admin</h1>

      <Resource
        value={subscribersResource}
        onRejected={() => <Noty message="Failed to fetch subscribers" type="error" />}
        onPending={() => <LoadingAnimation/>}
        onResolved={(res: ResourceResponse) => <table>
          <thead>
          <tr>
            <th>No.</th>
            <th>Email</th>
            <th>Website</th>
            <th>Joined</th>
          </tr>
          </thead>
          <tbody>
            {subscriberRows(res.data)}
          </tbody>
        </table>
        }
      ></Resource>
    </div>
  );
});

После добавления некоторых подписчиков мы должны увидеть таблицу, заполненную их информацией, при посещении этой страницы.

Страница отписки от рассылки

Обычно ссылки для отказа от подписки должны быть доступны в электронных письмах с информационными бюллетенями. Чтобы отписать пользователей, нам нужно передать параметры маршруту отписки.

Файловая маршрутизация Qwik аналогична другим платформам, поскольку мы передаем параметры маршрута, объявляя их как имена каталогов в квадратных скобках.

Чтобы передать параметры domain и email, мы настроили структуру каталогов для страницы отписки следующим образом.

└── routes
    ├── unsubscribe
    │   └── [email]
    │       └── [domain]
    │           └── index.tsx

Затем мы используем функцию Qwik useLocation() для получения объекта RouteLocation для активного местоположения, из которого мы получаем параметры email и domain. Мы используем эти два параметра, чтобы удалить данные пользователя из базы данных, чтобы отписать их от рассылки внутри другой серверной функции.

Весь процесс отказа от подписки автоматизирован благодаря тому, что unsubscribeFromNewsletter(), которая также является функцией Qwik server$, запускается при отображении компонента страницы в браузере. Мы делаем это, вызывая эту функцию внутри хука useBrowserVisibleTask().

import { createClient } from "@libsql/client";

export default component$(() => {
  const location = useLocation();
  const email = useSignal(location.params.email);
  const domain = useSignal(location.params.domain);
  const loading = useSignal(false);
  const notification = useStore({
    message: "",
    status: "",
  });

  const unsubscribeFromNewsletter = server$(async () => {
    const db = createClient({
      url: import.meta.env.VITE_DB_URL,
    });
    const deleteRecord = await db.execute(
      "delete from newsletters where email = ? and website like ?",
      [email.value, domain.value]
    );

    if (!deleteRecord.success) {
      return {
        success: false,
        message: "Sorry, something isn't right, please reload the page!",
      };
    }

    return {
      success: true,
      message: "Unsubscribed!",
    };
  });

  useBrowserVisibleTask$(async () => {
    loading.value = true;
    const res = await unsubscribeFromNewsletter();
    notification.message = res.message;
    notification.status = res.success ? "success" : "error";
    loading.value = false;
  });
});

Вот демонстрация того, что произойдет при отписке пользователей, начиная с представления почты.

Это все для менеджера рассылки. Посетите репозиторий проекта на GitHub, чтобы получить исходный код и узнать о нем больше.

Чтобы узнать больше о Qwik, посетите их официальную документацию. В настоящее время Turso находится в закрытом бета-тестировании, и вы можете присоединиться к нему, перейдя на веб-сайт ChiselStrike.