Day 35: Signup Invitations and Analytics

Now that we have a working MVP, there are a few more things to finish before onboarding our first users.
First, we can’t open an MVP to everyone. There’s a big chance that we’ll not have many users in the first weeks or months (that’s a startup reality), but we still need a controlled environment for the first batch of users. To control the number of signups, we need to modify our signup process and introduce a concept of invitation codes. We also need to send an email with an invitation code once we want to onboard a user.
Second, we need to add some app analytics. We do not have a back office (internal administration dashboard) at the moment because that’s not relevant for this stage of MVP. However, we need to be able to track pages users visit and actions users do in our dashboard to understand their behavior.
Invitation codes
We do not want to be too creative with invitation codes. We want to create a public signup page where you fill out the form and request access. Then, when we read the responses and decide to onboard a user, we send an email with an invitation code.
When you fill in the code, you sign up for Knowlo and start using the app without any additional changes.
Let’s see if CofounderGPT has any ideas on how to implement the invitation codes syste
Slobodan: What is the simplest way to add an invitation code system to our application? We do not want users to be able to sign up on their own at the beginning. Instead, they can fill out the form, and when we review it and are ready to onboard them, we should send them some kind of invitation code.
CofounderGPT: Creating an invitation code system can be broken down into a few basic steps:
- Database Design:
- Add a
Invitations
table (or a relevant GSI if following the single table design) in your DynamoDB to store the invitation codes. This table might contain attributes such as:code
,email
,status
, andexpirationDate
.
- Add a
- Frontend Changes:
- Requesting an Invite:
- Create a form for users to request an invite. This will typically ask for an email.
- Send this request to your backend (AppSync GraphQL mutation).
- Signup Using an Invite:
- Enhance the signup form to include a field for the invitation code.
- Upon submission, check for the validity of the invitation code on the backend.
- Requesting an Invite:
- Backend Changes:
- Handling Invite Request:
- When an invite request is received, store the email (or additional data if your form asks for more) with a default status like
PENDING
.
- When an invite request is received, store the email (or additional data if your form asks for more) with a default status like
- Generating an Invite:
- When you decide to grant an invitation, generate a unique code. This can be done using a UUID or any other mechanism that ensures uniqueness.
- Store this code in the DynamoDB
Invitations
table with the email, setting the status toSENT
. - Send the code to the user via email. Use an AWS service like SES (Simple Email Service) to send the email.
- Signup Using an Invite:
- When a signup request is received, check the DynamoDB table to verify the validity of the code (not expired, has status
SENT
, and matches the email). - If valid, allow the user to sign up and change the status of the invitation to
USED
.
- When a signup request is received, check the DynamoDB table to verify the validity of the code (not expired, has status
- Handling Invite Request:
- AWS CDK Changes:
- Add necessary resources for the new DynamoDB table/GSI.
- Grant required permissions for Lambda functions to access this new table/GSI.
- Ensure that the Lambda function has the IAM permissions required to send emails through SES.
Sample Code:
Generating an Invite:
import { v4 as uuidv4 } from 'uuid';
// This function will be triggered when you decide to grant an invite
export const generateInvite = async (email: string) => {
const inviteCode = uuidv4();
// Store in DynamoDB
await dynamoDb.put({
TableName: "YourTableName",
Item: {
PK: `INVITE#${inviteCode}`,
SK: `EMAIL#${email}`,
status: 'SENT',
expirationDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // 1 week from now
}
}).promise();
// Use SES to send the email
//...
};
Signup using Invite:
export const signUpUsingInvite = async (email: string, inviteCode: string) => {
const result = await dynamoDb.get({
TableName: "YourTableName",
Key: {
PK: `INVITE#${inviteCode}`,
SK: `EMAIL#${email}`
}
}).promise();
if (!result.Item || result.Item.status !== 'SENT' || new Date() > new Date(result.Item.expirationDate)) {
throw new Error("Invalid or expired invite code");
}
// Allow signup
// ...
// Update status to USED
await dynamoDb.update({
TableName: "YourTableName",
Key: {
PK: `INVITE#${inviteCode}`,
SK: `EMAIL#${email}`
},
UpdateExpression: "SET status = :status",
ExpressionAttributeValues: {
":status": "USED"
}
}).promise();
};
Make sure to incorporate proper error handling and adjust the codes based on your existing setup.
Excellent! That’s similar to what I planned to do.
Besides the basic questions about the user (name and email), we want to know more about their knowledge base, such as the knowledge base provider, a URL, and, ideally, a description of how they use the knowledge base.
We have most of these input fields in the setup form, so after a few modifications and copying some components, I created the following “Request access” page:

When users select the “Other” as their Knowledge base provider, we’ll ask them for the provider name:

Once the user requests access, we need to show a thanks page. As this is just a temporary flow, I don’t want to waste a lot of time on it, so something similar to the following will work fine:

Finally, we’ll need to ask the user to provide an invitation code during the actual signup. To do that, we’ll modify our signup form a bit and add an additional mandatory field:

That’s it for now. I’ll skip the implementation details because they are similar to the process described by CofounderGPT above.
Sending emails
The next step is integrating with a platform for sending invitation codes and other transaction emails (we’ll add them later).
I am very happy with Customer.io, a solution that we use for Vacation Tracker. It’s more than an email-sending platform because it allows you to define custom workflows based on events and actions (i.e., you can send a series of onboarding emails that depend on the exact features customers already configured, etc.).
However, the only problem is that Customer.io pricing starts at $100/month, which is too expensive for Knowlo’s current stage. So, we’ll go with another option until we reach the moment where spending $100/month on an email platform makes sense.
What are the other options we have? Let’s see if CofounderGPT has any good ideas.
Slobodan: What can we use for transactional emails? I like Customer.io, but it’s expensive for our current stage (it starts at $100/month). We need something easy to integrate and, ideally, free or cheap (until we start sending many emails each month). We need to send invitation codes and possibly a few other emails (not more than 3-4 per user), and we do not expect more than 100 users in the next 6 months.
CofounderGPT: Since we’re already using AWS for your infrastructure, the natural choice for transactional emails is Amazon SES (Simple Email Service). Given our projected usage (a few hundred emails over 6 months), using Amazon SES will be extremely cost-effective. The first 62,000 emails each month are free when sent from an Amazon EC2 instance or when sent from a Lambda function in the SES Free Usage Tier. After that, the price is $0.10 per 1,000 emails sent.
Here’s a basic guideline to integrate Amazon SES with our current stack:
1. Setting Up Amazon SES
- Verify Your Domain: Before you can send emails via SES, you need to verify your domain. This proves that you own the domain and can send emails from it.
- Request Sending Limit Increase: By default, SES is in sandbox mode, which restricts you from sending emails to non-verified email addresses. Once you’ve tested and you’re ready to send to real users, submit a request to AWS to increase your sending limits and move out of the sandbox environment.
2. Send Emails from Lambda Functions
Here’s a basic way to send an email using Node.js, TypeScript, and the AWS SDK within a Lambda function:
import { SES } from "aws-sdk";
const ses = new SES();
export const sendEmail = async (toEmail: string, subject: string, body: string) => {
const params = {
Destination: {
ToAddresses: [toEmail]
},
Message: {
Body: {
Text: {
Charset: "UTF-8",
Data: body
}
},
Subject: {
Charset: "UTF-8",
Data: subject
}
},
Source: "your-verified-email@example.com" // Your verified SES email
};
try {
await ses.sendEmail(params).promise();
} catch (error) {
console.error("Error sending email", error);
throw error;
}
};
3. Integrate with AWS CDK
Ensure your Lambda function has the necessary permissions to send emails via SES:
import { Function } from "@aws-cdk/aws-lambda";
import { PolicyStatement, Effect } from "@aws-cdk/aws-iam";
const yourLambdaFunction: Function = /* your lambda function setup here */;
// Grant permissions to SES
yourLambdaFunction.addToRolePolicy(
new PolicyStatement({
effect: Effect.ALLOW,
actions: ["ses:SendEmail", "ses:SendRawEmail"],
resources: ["*"]
})
);
4. Email Content and Templates
While the example above uses a simple text email, SES supports HTML content and templated emails too. For a better user experience, especially for invitations, you may want to design attractive HTML templates.
Alternatives to SES
If for some reason, SES doesn’t meet your needs, here are some alternatives:
- SendGrid: They offer a free tier with 100 emails/day.
- Mailgun: Their “Flex” plan charges based on usage, and you get the first 10,000 emails free monthly.
- Mailjet: They offer a free tier with 200 emails/day limit.
Each of these services offers APIs and integrations that should be straightforward to use in your Node.js/TypeScript backend.
Slobodan: AWS SES sounds excellent. Thanks!
Can you create a simple email template for us to use with AWS SES? It should look nice and pass the following information:
“Welcome to Knowlo private beta! Here’s your invitation code:
[ CODE ]
Go to https://app.knowlo.co/signup and complete your signup using the invitation code above.
If you have any questions or issues, please reply to this email.”
Feel free to change the text as long as it passes the accurate information.
CofounderGPT: Certainly! Here’s a basic HTML email template that you can use with AWS SES:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 40px 0;
}
.email-container {
width: 100%;
max-width: 600px;
margin: 0 auto;
background-color: #ffffff;
border-radius: 8px;
padding: 20px 30px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.header {
text-align: center;
padding-bottom: 20px;
}
.invitation-code {
background-color: #e8f5e9;
padding: 15px;
border: 1px solid #a5d6a7;
text-align: center;
font-size: 24px;
margin: 20px 0;
word-break: break-all;
}
a {
color: #1e88e5;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.footer {
font-size: 14px;
color: #555;
padding-top: 20px;
}
</style>
</head>
<body>
<div class="email-container">
<div class="header">
<h2>Welcome to Knowlo private beta!</h2>
</div>
<p>Thank you for showing interest in our platform. We're excited to have you on board. Here's your exclusive invitation code:</p>
<div class="invitation-code">
[ CODE ]
</div>
<p>Go to <a href="https://app.knowlo.co/signup">https://app.knowlo.co/signup</a> and complete your signup using the invitation code above.</p>
<p class="footer">If you have any questions or issues, please reply to this email.</p>
</div>
</body>
</html>
You can then integrate this HTML into your Lambda function. Replace [ CODE ]
with the actual code you generate for your users:
const htmlContent = `
...[above HTML content]...
<div class="invitation-code">
${yourGeneratedCode}
</div>
...[rest of the HTML content]...
`;
This design is quite simple but effective for its purpose. Remember, when it comes to email, simpler designs are often better due to the varying ways email clients render HTML.
I rendered the HTML to see the output, and it looks good!

Creating email templates was one of the most annoying things you could do in web development 10 years ago. It was even more annoying than writing JavaScript code for Internet Explorer 6. I know that email platforms have evolved in the last decade, and (I hope) this process is easier now, but still, if an AI can write effective email templates, our lives will be much easier!
So, let’s test this email template before declaring a victory. I found the following website to test and validate HTML Email templates: https://www.htmlemailcheck.com.
It seems that the template is good enough! There’s one HTML meta tag that requires minor modification, a CSS style that we need to add for Outlook.com, and some HTML and CSS warnings. Thanks CofounderGPT!

Again, I’ll skip the implementation details because they follow the guidelines provided by CofounderGPT above. Sending an email with an invitation code is done! ✅
Analytics
Setting up useful product analytics requires at least one dedicated article, if not a whole book. Hopefully, we’ll reach the stage where writing that article makes sense. Today, we want to do a basic integration of a tool that will give us initial visibility.
The goal of the product analytics tool integration is to be able to follow what users and potential users do on our platform. From the moment they first time visit our app through their product lifecycle.
I have some preferences based on our experience with Vacation Tracker, but as always, let’s start by asking CofounderGPT for its opinion.
Slobodan: We need to implement product analytics. We can’t use Google Analytics because we need the ability to send events from both the front end and back end and identify users. What options do we have? The tool must be free or cheap for our current phase and easy to integrate with our front end and back end systems. Also, a non-developer should be able to query the data and create reports easily.
CofounderGPT: Based on your requirements, here are some product analytics solutions that can be integrated into both front-end and back-end systems and also offer user-friendly querying and reporting:
- Amplitude:
- Features: Real-time product analytics, user segmentation, retention reporting, and more.
- Integration: Has SDKs for both front-end (React) and back-end (Node.js).
- Cost: Offers a free tier that supports 10 million actions per month, which might be more than enough for your initial phase.
- Non-developer Access: Its interface is designed to be user-friendly for non-developers.
- Mixpanel:
- Features: Provides event tracking, funnels, user segmentation, retention analysis, etc.
- Integration: SDKs available for React (front-end) and server-side libraries for Node.js.
- Cost: Offers a limited free tier and then transitions to a usage-based model.
- Non-developer Access: Provides a GUI to create reports and analyze data without needing developer assistance.
- Heap:
- Features: Automatic tracking of all events without manual coding, funnels, retention, and more.
- Integration: Client libraries for React and server-side for Node.js.
- Cost: Has a free tier for startups, but you’ll need to check if it aligns with your expected usage.
- Non-developer Access: Designed for easy use by product managers and other non-technical stakeholders.
- Segment (as a data router):
- Features: Segment isn’t an analytics tool on its own but a data integration platform. It captures data from your app and routes it to hundreds of tools, including the above-mentioned analytics platforms. This can be beneficial if you decide to switch or add more tools later.
- Integration: Libraries for both React and Node.js.
- Cost: Pricing is based on monthly tracked users, but they do offer a free tier.
- Non-developer Access: More on the technical side, but once integrated, it can push data to user-friendly platforms.
- AWS Pinpoint:
- Features: Since you’re already on AWS, Pinpoint offers analytics and user engagement reporting.
- Integration: SDKs for front-end and server-side.
- Cost: Based on events recorded and has a free tier.
- Non-developer Access: AWS’s dashboard might be less user-friendly compared to specialized tools, but it can provide raw data that could be further visualized using Amazon Quicksight or other BI tools.
Considerations:
- Data Privacy: Ensure the tool aligns with your privacy policy and user data handling practices. This is especially important if handling PII or sensitive information.
- Future Scaling: Even though the focus is on the current phase, keep in mind future scalability and how easy it would be to migrate to a more comprehensive plan or another tool if needed.
Given your current stack and requirements, starting with a tool like Amplitude might be a good fit due to its robust free tier, ease of integration, and user-friendly interface for non-developers. However, you might want to evaluate a couple of these tools firsthand to see which one aligns best with your specific needs.
These are some excellent recommendations. We used all of these tools except Heap at some point for Vacation Tracker.
Here are a few notes from my side based on our experience:
- Mixpanel and Amplitude are great and have a limited but generous free tier.
- Mixpanel and Amplitude also have SOC2 type 2 reports you can request and read. These reports are essential for larger startups and companies because these reports contain information about security and other important pieces.
- I heard many good things about Heap, so we should probably try it at some point.
- Segment is great when you have a larger marketing team that does a lot of experiments. They offer a startup program, and you can get Segment for free for at least one year (at least they had it a few years ago). However, the Segment app can be pretty expensive after that.
- AWS Pinpoint sounds great on paper, but the usability and flexibility are not even close to Mixpanel and Amplitude. Its main advantage is that the data stays in the AWS ecosystem, but it’s not worth it.
Some of the suggestions from CofounderGPT are aligned with my personal preferences. So, I’ll pick Mixpanel as our primary product analytics tool.
Setting up Mixpanel is not hard. They offer a variety of libraries and tutorials. However, we switched and tested multiple tools for Vacation Tracker, so I like abstracting the analytics with my interface (just a thin wrapper, nothing big). In the end, the business logic of our application should not be tracking a Mixpanel event. Instead, it should send an analytics event, and some adapter should decide where to route it.
So, how do we do that?
There are many options. Segment is one of them. However, I’ll go with something that requires writing some code but does not add a few hundred dollars to our monthly bill in a few years. That’s https://getanalytics.io!
After creating a Mixpanel account, I followed this short tutorial: https://getanalytics.io/plugins/mixpanel/. Here are the results:

It’s not fancy, but it’s good enough for this phase.
Besides the page tracking, we’ll need to track events. And there’s an important decision to make: should we track these events from the front end or from the back end?
What’s the difference? Front end tracking gives us more information about users’ location, browsers, UTM campaigns, etc. However, if they have an ad blocker, there’s a big chance that we lose analytics data.
On the other hand, back end tracking gives us less data, but it works regardless of users’ ad blockers.
We’ll probably go with a mixture of both: tracking page visits from the front end and events from the back end.
We’ll probably go with a mixture of both: tracking page visits from the front end and events from the back end. Soon, we’ll need to think about opting out of analytics, etc. However, that’s definitely not a decision and a feature we want to make before validating that we are on a good track to find a product-market fit.
What’s Next?
The next step is event-tracking implementation. Once that’s done, we will finally deploy the production version of the application and set up the domain. A to-do list of improvements is still big, but most of them can come after the launch.
Scoreboard
Time spent today: 8h
Total time spent: 238h
Investment today: $0
Total investment: $1,306.60USD
Beta list subscribers: 96
Paying customers: 0
Revenue: $0
Comments are closed