Skip to content

Commit

Permalink
feat: Add blog (#492)
Browse files Browse the repository at this point in the history
* add basic blog

* finish blog

* add everything

* Add nav button

* fix lint

* fix window

* use vercel

* try await

* stateless nav
  • Loading branch information
storm1729 authored Dec 28, 2024
1 parent 42d7ae6 commit db4b4b9
Show file tree
Hide file tree
Showing 20 changed files with 510 additions and 89 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# `@reacherhq/webapp`

This is the new Reacher webapp, currently deployed at https://app.reacher.email.

## Color Palette

- Reacher Purple: #6B63F6
57 changes: 57 additions & 0 deletions _posts/smtp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
title: What's Reacher's Secret for Accuracy?
lastUpdated: 28.12.2024
author:
name: Amaury
description: Reacher employs SMTP email verification to validate email addresses through commands like EHLO and RCPT TO. Positive responses confirm validity, while negative ones indicate issues. This method improves deliverability, reduces bounce rates, and supports efficient scaling of email verification.
ogImage:
url: /img/blog/smtp/cover.jpg
---

Reacher is [open-source](https://github.com/reacherhq/check-if-email-exists), so there's really no secret. But for those less familiar with the technical aspects, I'll explain a bit more here, hopefully in a beginner-friendly way. The secret is called **"SMTP email verification"**.

### How SMTP Email Verification Works

SMTP email verification involves a series of commands to interact with the recipient's mail provider (Outlook, Gmail, Yahoo...).

Let's take a look at a typical conversation between a sender (Reacher) and the provider of the email you want to check. In our case, we want to verify `[email protected]`, the provider being Gmail. Reacher initiates the protocol, called "SMTP", or "Simple Mail Transfer Protocol".

> **Reacher:** Hi there, Gmail! I'm trying to send an email address. Can I connect to your SMTP server?
> **Gmail:** Ah, hello! Please send an **EHLO** command to introduce yourself.
> **Reacher:** Got it! Here it goes: `EHLO reacher.email`.
> **Gmail** _(checks if reacher.email is a valid domain)_: Hello Reacher! Nice to meet you. I've received your EHLO command, your IP reputation seems good. You're now connected to my SMTP server.
> **Reacher:** Great, thanks! Next, I'll send a **MAIL FROM** command to specify the my own email address as the sender. Here it is: `MAIL FROM: <[email protected]>`
> **Gmail:** Received! I've got the sender's email address. Now, please go ahead and send the **RCPT TO** command with the recipient's email address.
> **Reacher:** Here it is: `RCPT TO: <[email protected]>`.
> **Gmail:** Okay, I've received the RCPT TO command. Let me check if the recipient's email address is valid... _(pause)_ Ah, yes! The recipient's email address is valid and deliverable.
> **Reacher:** Excellent, bye bye!
> **Gmail:** _(puzzled)_ ...?
It's important to note that Reacher doesn't actually send the email at the end of the conversation, but rather terminates it quickly. This is enough to check if the email is deliverable. If this process is repeated excessively, the email provider may flag Reacher as a spam user. This is where [Proxies](https://docs.reacher.email/self-hosting/proxies) play a crucial role.

### Parsing Responses for Deliverability

Reacher analyzes the responses from the **RCPT TO** command to determine deliverability. This analysis involves checking response codes and messages:

- **Positive Responses**: Codes like `250 OK` indicate a valid address.
- **Negative Responses**: Codes like `550` or `553` indicate invalid or non-existent addresses.
- **Ambiguous Responses**: Temporary errors (e.g., `421` or `450`) suggest issues like rate limits or server unavailability.

Some servers may use catch-all configurations or impose SMTP restrictions, such as requiring authentication or blocking address verification attempts. These behaviors can make the verification process more complicated, but Reacher provides solutions to handle these challenges effectively.

### The Advantages of SMTP Email Verification

1. **Enhanced Deliverability**: With real-time email verification, we can determine if the email is deliverable _right now_. This significantly improves accuracy.
2. **Reduced Bounce Rates**: Fewer bounces protects the sender's reputation and prevent blacklistings, a crucial feature of Reacher.
3. **Scaling Efficiency**: As long as we make sure the verifying party's IP is good (for example using proxies), then we can make a large number of parallel requests.

SMTP email verification offers a methodical approach to ensuring email list hygiene and optimizing communication strategies. By utilizing commands like EHLO/HELO, MAIL FROM, and RCPT TO, **Reacher** facilitates precise and effective address validation.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"@geist-ui/react-icons": "^1.0.1",
"@getbrevo/brevo": "^2.2.0",
"@hcaptcha/react-hcaptcha": "^1.9.2",
"@mantine/core": "^7.15.2",
"@mantine/hooks": "^7.15.2",
"@reacherhq/api": "^0.3.10",
"@sentry/nextjs": "^8",
"@stripe/stripe-js": "^1.52.0",
Expand All @@ -47,9 +49,10 @@
"cors": "^2.8.5",
"date-fns": "^2.30.0",
"encoding": "^0.1.13",
"gray-matter": "^4.0.3",
"mailgun-js": "^0.22.0",
"markdown-pdf": "^11.0.0",
"marked-react": "^2.0.0",
"marked-react": "^3.0.0",
"mustache": "^4.2.0",
"negotiator": "^0.6.3",
"next": "^14.0.4",
Expand Down
Binary file added public/img/blog/cover2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/img/blog/smtp/cover.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
99 changes: 99 additions & 0 deletions src/app/[lang]/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { dictionary } from "@/dictionaries";
import { Button, Container, Text } from "@mantine/core";
import Markdown from "marked-react";
import { DLink } from "@/components/DLink";
import Image from "next/image";
import { getAllPosts, getPostBySlug } from "@/util/blog";
import { Metadata } from "next";
import { notFound } from "next/navigation";
import { Footer } from "@/components/Footer";
import { Nav } from "@/components/Nav/Nav";

// Cover ideas:
// - https://unsplash.com/s/photos/geometric-pattern
// - https://stephaniewalter.design/blog/how-to-make-your-blog-images-stand-out-reflect-your-identity/

type Params = {
params: Promise<{
lang: string;
slug: string;
}>;
};

export async function generateMetadata(props: Params): Promise<Metadata> {
const params = await props.params;
const blogPost = getPostBySlug(params.slug);
if (!blogPost) {
return notFound();
}

return {
title: blogPost.title,
description: blogPost.description,
openGraph: {
title: blogPost.title,
images: [blogPost.ogImage.url],
},
};
}

export default async function BlogPost(props: Params) {
const { lang, slug } = await props.params;
const d = await dictionary(lang);
const blogPost = getPostBySlug(slug);
if (!blogPost) {
return notFound();
}

return (
<>
<Nav d={d} page="blog" />

<Container mt="xl" size="45rem">
<div className="text-center">
<h1>{blogPost.title}</h1>
<Text mb="xl">
{d.blog.author}: {blogPost.author.name}.{" "}
{d.blog.last_updated}: {blogPost.lastUpdated}.
</Text>
</div>

<div
style={{
height: "400px",
width: "45rem",
overflow: "hidden",
position: "relative",
}}
>
<Image
src={blogPost.ogImage.url}
alt={blogPost.title}
fill
objectFit="cover"
/>
</div>

<Markdown>{blogPost.content}</Markdown>
</Container>

<div className="text-center">
<DLink d={d} href="/dashboard/verify">
<Button mt="xl" size="lg">
{d.blog.cta}
</Button>
</DLink>
</div>

<Footer d={d} />
</>
);
}

export async function generateStaticParams() {
const posts = getAllPosts();

return posts.map((post) => ({
slug: post.slug,
}));
}
4 changes: 3 additions & 1 deletion src/app/[lang]/dashboard/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Footer } from "@/components/Footer";
import { Nav } from "@/components/Nav/Nav";
import { dictionary } from "@/dictionaries";
import { getSession } from "@/supabase/supabaseServer";
import React from "react";

export const metadata = {
Expand All @@ -15,10 +16,11 @@ export default async function Layout({
params: { lang: string };
}) {
const d = await dictionary(lang);
const session = await getSession();

return (
<>
<Nav d={d} />
<Nav d={d} page="dashboard" user={session?.user} />
{children}
<Footer d={d} />
</>
Expand Down
4 changes: 3 additions & 1 deletion src/app/[lang]/legal/LegalPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Page } from "@/components/Geist";
import { dictionary } from "@/dictionaries";
import { Footer } from "@/components/Footer";
import { removeFrontMatter } from "@/components/Markdown";
import { getSession } from "@/supabase/supabaseServer";

export async function LegalPage(
contentUrl: string,
Expand All @@ -18,10 +19,11 @@ export async function LegalPage(
const { data } = await axios.get(contentUrl);
const content = removeFrontMatter(data);
const d = await dictionary(lang);
const session = await getSession();

return (
<>
<Nav d={d} />
<Nav d={d} user={session?.user} />
<Page>
<Md>{content}</Md>
</Page>
Expand Down
2 changes: 1 addition & 1 deletion src/app/[lang]/pricing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default async function Pricing({

return (
<>
<Nav d={d} />
<Nav d={d} user={session?.user} />
<Spacer h={5} />
<Text className="text-center" h2>
{d.pricing.title}
Expand Down
20 changes: 0 additions & 20 deletions src/app/[lang]/pricing_new/StripePricingTable.tsx

This file was deleted.

36 changes: 0 additions & 36 deletions src/app/[lang]/pricing_new/page.tsx

This file was deleted.

28 changes: 26 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,40 @@
import "./global.css";
import "./geist.gen.css";
import "@mantine/core/styles.css";

import { GeistProvider, myTheme } from "@/components/Geist";
import Script from "next/script";
import { createTheme, MantineProvider } from "@mantine/core";

export const metadata = {
title: {
template: "%s | Reacher Email Verification",
default: "Reacher Email Verification",
},
description:
"Reacher is a simple, fast, accurate email verification tool to reduce your bounce rate and avoid spam sign-ups. We check SMTP responses, MX records, catch-all and disposable addresses.",
"Reacher is an open-source, fast, and accurate email verification tool designed to reduce bounce rates and prevent spam sign-ups. It checks SMTP responses, MX records, catch-all, and disposable addresses for reliable results.",
};

const mantineTheme = createTheme({
black: "#3a3a3a",
colors: {
purple: [
// https://mantine.dev/colors-generator/?color=605acc
"#efeeff",
"#dcdaf9",
"#b5b3ea",
"#8d89dc",
"#6b65cf",
"#554fc8",
"#4943c6",
"#3b35af",
"#332f9e",
"#29278c",
],
},
primaryColor: "purple",
});

export default function RootLayout({
children,
}: {
Expand All @@ -26,7 +48,9 @@ export default function RootLayout({
</head>
<body>
<GeistProvider themes={[myTheme]} themeType="default">
{children}
<MantineProvider theme={mantineTheme}>
{children}
</MantineProvider>
</GeistProvider>
<Script
async
Expand Down
2 changes: 1 addition & 1 deletion src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export function Footer({ d }: { d: Dictionary }): React.ReactElement {
<Spacer />

<div className={styles.bottom}>
<Text small>© Reacher 2020-2023</Text>
<Text small>© Reacher 2020-2025</Text>
<a
href="https://vercel.com?utm_source=reacherhq&utm_campaign=oss"
target="_blank"
Expand Down
5 changes: 2 additions & 3 deletions src/components/Markdown.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { CustomReactRenderer } from "marked-react/dist/ReactRenderer";
import type { ReactNode } from "react";
import { DLink } from "./DLink";
import { Dictionary } from "@/dictionaries";

export const SpanRenderer: CustomReactRenderer = {
export const SpanRenderer = {
paragraph(children: ReactNode) {
return <span>{children}</span>;
},
};

export function LinkRenderer(d: Dictionary): CustomReactRenderer {
export function LinkRenderer(d: Dictionary) {
return {
link(href: string, text: ReactNode) {
return (
Expand Down
Loading

0 comments on commit db4b4b9

Please sign in to comment.