# Angular contact form that sends email (no backend)

> An Angular contact form that emails you with no backend: a standalone component, reactive forms, HttpClient and the key in environment.ts. Angular 22 syntax.

Source: https://formsmtp.com/guides/angular-contact-form · Last updated November 2, 2026

Written for Angular 22, the current version on angular.dev. It uses `inject()`, signals and the `@if` block, all available since Angular 17.

## 1. Create a FormSMTP project

[Sign up free](https://app.formsmtp.com/#/signup) and add a project. Pick your email provider so the SMTP host and port fill in, enter your login, and click *Test connection*. Not sure which provider? Your existing mailbox works: see the [Gmail](https://formsmtp.com/guides/gmail-smtp-contact-form) or [Zoho](https://formsmtp.com/guides/zoho-smtp-contact-form) guides.

## 2. Lock the project to your site

In *Settings → Security*, set **Allowed websites** to `https://yourapp.com` and **Allowed recipients** to your own address. Now the key in your page can only email you, only from your site, and only up to your hourly limit.

`ng serve` runs on `http://localhost:4200`. Add it to Allowed websites while you develop, or use a separate FormSMTP project for development.

## 3. Put the endpoint in environment.ts

If your project has no environment files yet, run `ng generate environments`. It creates `environment.ts` (production) and `environment.development.ts`, and sets up the file replacement in `angular.json`.

```ts
// src/environments/environment.ts
export const environment = {
  formsmtpEndpoint: "https://app.formsmtp.com/api/v1/form/YOUR_FORM_KEY",
};
```

Put the same line, with a development key if you like, in `environment.development.ts`. Always import the base file; the CLI swaps in the right one when it builds.

## 4. Make HttpClient available

In Angular 21 and later, `HttpClient` can be injected without any setup. On earlier versions, add it to your app config:

```ts
// src/app/app.config.ts
import { ApplicationConfig } from "@angular/core";
import { provideHttpClient } from "@angular/common/http";

export const appConfig: ApplicationConfig = {
  providers: [provideHttpClient()],
};
```

## 5. Create the component

```ts
// src/app/contact-form/contact-form.ts
import { Component, inject, signal } from "@angular/core";
import { HttpClient, HttpErrorResponse, HttpParams } from "@angular/common/http";
import { NonNullableFormBuilder, ReactiveFormsModule, Validators } from "@angular/forms";
import { environment } from "../../environments/environment";

type FormReply = { success: boolean; error?: string };

@Component({
  selector: "app-contact-form",
  imports: [ReactiveFormsModule],
  templateUrl: "./contact-form.html",
})
export class ContactForm {
  private http = inject(HttpClient);
  private loadedAt = Date.now();

  protected status = signal<"idle" | "sending" | "sent" | "error">("idle");
  protected error = signal("");

  protected form = inject(NonNullableFormBuilder).group({
    name: ["", Validators.required],
    email: ["", [Validators.required, Validators.email]],
    message: ["", [Validators.required, Validators.maxLength(5000)]],
    _gotcha: [""], // honeypot: stays empty for people
  });

  protected submit() {
    if (this.form.invalid) {
      this.form.markAllAsTouched();
      return;
    }
    this.status.set("sending");
    const body = new HttpParams({
      fromObject: { ...this.form.getRawValue(), _subject: "New message from the website", _t: this.loadedAt },
    });
    this.http
      .post<FormReply>(environment.formsmtpEndpoint, body, { headers: { Accept: "application/json" } })
      .subscribe({
        next: (reply) => {
          if (!reply.success) return this.fail(reply.error);
          this.status.set("sent");
          this.form.reset();
        },
        error: (err: HttpErrorResponse) => this.fail(err.error?.error),
      });
  }

  private fail(message?: string) {
    this.error.set(message ?? "Something went wrong. Please try again.");
    this.status.set("error");
  }
}
```

```html
<!-- src/app/contact-form/contact-form.html -->
@if (status() === "sent") {
  <p>Thanks! We'll get back to you soon.</p>
} @else {
  <form [formGroup]="form" (ngSubmit)="submit()">
    <label>Name <input formControlName="name" autocomplete="name" /></label>
    @if (form.controls.name.touched && form.controls.name.invalid) {
      <small>Please enter your name.</small>
    }
    <label>Email <input formControlName="email" type="email" autocomplete="email" /></label>
    @if (form.controls.email.touched && form.controls.email.invalid) {
      <small>Please enter a valid email address.</small>
    }
    <label>Message <textarea formControlName="message" rows="5"></textarea></label>
    <input formControlName="_gotcha" style="display: none" tabindex="-1" autocomplete="off" />

    <button type="submit" [disabled]="status() === 'sending'">
      {{ status() === "sending" ? "Sending…" : "Send" }}
    </button>
    @if (status() === "error") {
      <p role="alert">{{ error() }}</p>
    }
  </form>
}
```

Components are standalone by default in current Angular, so the component imports `ReactiveFormsModule` itself. Use it anywhere with `<app-contact-form />` after adding `ContactForm` to the parent component’s `imports`.

## Why HttpParams?

Passing `HttpParams` as the body makes Angular send the fields URL-encoded, like a normal HTML form. That’s a “simple” cross-origin request, so the browser skips the CORS preflight. The `Accept: application/json` header tells FormSMTP to answer with `{ success, error }` instead of a thank-you page. `_t` is when the component loaded; FormSMTP drops submissions made within two seconds of it, which is how bots behave.

Prefer JSON? Pass a plain object instead and Angular sends `application/json`. The browser then sends a preflight first, which FormSMTP answers, so both work:

```ts
this.http.post<FormReply>(environment.formsmtpEndpoint, {
  ...this.form.getRawValue(),
  _subject: "New message from the website",
  _t: this.loadedAt,
});
```

Don’t post a `FormData` object. Angular sends it as `multipart/form-data`, which the form endpoint rejects with “File uploads are not supported”.

## Server-side rendering and prerendering

Nothing changes with Angular SSR or prerendering. The component renders on the server, but `submit()` only runs in the browser, which posts straight to FormSMTP. You don’t need an Express route or a Firebase function.

## Troubleshooting

- **NullInjectorError: No provider for HttpClient!** You’re on Angular 20 or earlier without `provideHttpClient()` in `app.config.ts`.
- **403 “This form isn’t allowed on http://localhost:4200”:** add the dev origin to Allowed websites, or use a development project.
- **“Http failure response for … 0 Unknown Error”:** the request never got a response. Check the endpoint URL in `environment.ts`, and whether a browser extension blocked it.
- **415 “File uploads are not supported”:** the body was a `FormData` object. Use `HttpParams` or a plain object.
- **429 “Too many submissions”:** one visitor sent several messages in a short time. The limit protects you from floods; wait a few minutes.

## FAQ

### Is it safe to put the key in environment.ts?

Yes, as long as it’s the form key (`fk_`). Everything in environment.ts ends up in your JavaScript bundle, and the form key is made for that: it only emails you, and you can lock it to your domain. Never put the API key (`fs_`) there.

### Do I need a backend or Firebase function?

No. The browser posts to FormSMTP over HTTPS, and FormSMTP sends through your SMTP account. Your Angular app can be served as static files from any host.

### Can I use template-driven forms or Signal Forms?

Yes. The form API only changes how you collect and validate the fields. The HttpClient call that sends them is the same.

### Why did my message land in spam?

Usually the From address and the SMTP account don’t match, or the domain has no SPF/DKIM records. Send from the address you log in with, and check the Deliverability panel in FormSMTP’s Settings.

## More guides

- [Next.js contact form with email (App Router)](https://formsmtp.com/guides/nextjs-contact-form)
- [Astro contact form that sends email](https://formsmtp.com/guides/astro-contact-form)
- [React contact form that sends email (Vite, CRA, any React)](https://formsmtp.com/guides/react-contact-form)
- [Vue contact form that sends email (Vue 3 & Nuxt)](https://formsmtp.com/guides/vue-contact-form)
