Engineering

How we implemented link-sharing using Dub.co

How we implemented link-sharing using Dub.co

Earlier this week Dub.co shared our customer story on why we choose Dub as our link-sharing infrastructure.


In this blog post where gonna share a little more in details how we implemented this functionality.


We have some features like Time Tracker, Reports and files from Vault that our users shares outside their company and with that we have an authorization layer on our side using Supabase, but these links are often really long because they include a unique token.


Our solution was to implement Dub to generate unique short URLs.


How we implemented sharing for our reports


Midday - Overview


If you look closely you can se our link looks like this: https://go.midday.ai/5eYKrmV


When the user clicks Share we execute a server action using the library next-safe-action

that looks like this:

const createReport = useAction(createReportAction, {
  onError: () => {
    toast({
      duration: 2500,
      variant: "error",
      title: "Something went wrong pleaase try again.",
    });
  },
  onSuccess: (data) => {
    setOpen(false);

    const { id } = toast({
      title: "Report published",
      description: "Your report is ready to share.",
      variant: "success",
      footer: (
        <div className="mt-4 space-x-2 flex w-full">
          <CopyInput
            value={data.short_link}
            className="border-[#2C2C2C] w-full"
          />

          <Link href={data.short_link} onClick={()=> dismiss(id)}>
            <Button>View</Button>
          </Link>
        </div>
      ),
    });
  },
});

The nice thing with next-safe-action is that you get callbacks on onError and onSuccess so in this case we show a toast based on the callback.


The action is pretty straightforward too, we first save the report based on the current parameters (from, to and type) depending on what kind of report we are creating.


We save it in Supabase and get a id back that we use to generate our sharable URL.

const dub = new Dub({ projectSlug: "midday" });

export const createReportAction = action(schema, async (params) => {
  const supabase = createClient();
  const user = await getUser();

  const { data } = await supabase
    .from("reports")
    .insert({
      team_id: user.data.team_id,
      from: params.from,
      to: params.to,
      type: params.type,
      expire_at: params.expiresAt,
    })
    .select("*")
    .single();

  const link = await dub.links.create({
    url: `${params.baseUrl}/report/${data.id}`,
    rewrite: true,
    expiresAt: params.expiresAt,
  });

  const { data: linkData } = await supabase
    .from("reports")
    .update({
      link_id: link.id,
      short_link: link.shortLink,
    })
    .eq("id", data.id)
    .select("*")
    .single();

  const logsnag = await setupLogSnag();

  logsnag.track({
    event: LogEvents.OverviewReport.name,
    icon: LogEvents.OverviewReport.icon,
    channel: LogEvents.OverviewReport.channel,
  });

  return linkData;
});

With the combination of server actions, Supabase and Dub we can create really beautiful URLs with analytics on top.


You can find the source code for this in our repository here.

Engineering

Why Midday trusts Resend to send financial notifications

Why Midday trusts Resend to send financial notifications

From sign up, to implementing the SDK, Resend was different - we were setup in less than an hour.


Here at Midday, we care deeply about design, and we knew from the beginning that we wanted branded emails with reusable components.


By integrating react.email with Tailwind CSS, we've crafted an optimal user experience. This combination has been transformative, allowing us to enable our local development and online hosting.


The transition to Resend was strikingly smooth. I'm used to other services where you need to request access, quotes, and other information before getting started. From sign up, to implementing the SDK, Resend was different - we were setup in less than an hour.


We use Resend to ensure that our clients receive notifications for new bank transactions, invitations, and other significant interactions. What truly sets Resend apart for us are the continuous changes and improvements, like the Batch API, which specifically helped optimize our invite system.


I didn't think that you could further innovate an email API, but Resend keeps shipping features that we did not know we needed.


We haven't had the need to contact Resend for support yet, but the team has supported us while we shared our journey building emails on X, which truly showed me how they differ from other email providers in the best way.


I'm really excited for our launch, along side my co-founder Viktor Hofte.


Resend is a game changer when it comes to developer experience and design. If you care deeply about your product you should use Resend.


This is a cross post from Resend

Engineering

Why Midday trusts Resend to send financial notifications

Why Midday trusts Resend to send financial notifications

At Midday, we use background jobs extensively. Here's an in-depth exploration of our approach.


Transactions Setup

Upon successful user authentication of their bank, we initiate the creation of a bank_connection containing essential data and provider information (Plaid, GoCardLess, Teller). This facilitates subsequent retrieval of transactions on their behalf. Additionally, we store bank connection accounts in bank_accounts, along with an enable/disable flag, enabling us to determine which accounts to fetch transactions from.

Once bank_connection and bank_accounts are securely stored in the database, we trigger the transactions-setup job. This job orchestrates the initial synchronization from the providers through batch requests, ensuring resilience against extensive transaction loads. Moreover, it dynamically schedules intervals for running transactions-sync every hour for each team. Clients subscribe to the eventId to track the completion of the initial sync process.


Transactions Sync

The transactions-sync task fetches transactions for each team hourly. A team may have multiple connected bank accounts, requiring one request per account. We aim to consolidate new transactions into a single collection for notification purposes. Transactions are limited to the last 30 days, streamlining data retrieval.

This approach offers a 30-day window for rectifying any potential errors and acquiring new transactions. Moreover, teams can seamlessly enable/disable accounts between sync runs, as transactions are fetched exclusively for enabled accounts. If a team_id is not found, indicating deletion, the scheduler is promptly removed.


Process Document

Upon receipt of a new inbound email from Postmarks, we upload the attachments (invoices) to Supabase and create an inbox record. Subsequently, we trigger the process-document job, responsible for parsing the invoice using Google Document AI to extract vital information such as due_date, amount, and currency. Successful extraction triggers the match-inbox event.


Match Inbox

The match-inbox process utilizes the inboxId, teamId, and amount to verify transactions matching the invoice amount, accounting for sign differences (transactions are always signed, whereas invoices are not). This verification extends back 45 days, considering cases where no attachments are available yet. Currently, encountering multiple matches necessitates further validation, a feature we plan to address in future UI enhancements.


Export Transactions

The export-transactions task retrieves selected transactionIds, gathers their attachments, compiles a CSV file, and uploads it to our Vault. Clients subscribe to monitor the progress status of this operation.


GitHub

You can find all of our jobs in our GitHub repository.

Update

The Early Adopter Plan

The Early Adopter Plan

Being an Early Adopter

We are currently collaborating with our first group of beta users to refine our product and find the right fit for the market. If you're interested in shaping the future of Midday and becoming an early adopter, feel free to join our community. There's no obligation to contribute, and we're thrilled that you want to try out the system.


During our private beta phase, you may encounter some bugs, but we genuinely want all your feedback.


The Early Adopter Plan

  • This plan includes access to all features.
  • Priced at $30 per month but free while in beta.
  • Your subscription price will remain unchanged for life as a token of appreciation for supporting Midday in its early stage, regardless of the additional features we introduce.

Community-Driven Development

As we continue to enhance Midday, we highly value your input and ideas. Here are three ways you can share your thoughts with us:

Stress free by midday.

Midday provides you with greater insight into your business and
automates the boring tasks, allowing you to focus on what you love to do instead.