Angular contact forms
without a mail server.
Use Reactive Forms and HttpClient for the UI while Form Plume handles notifications, stored submissions, spam protection, file uploads, integrations, and signed webhooks.
submit() {
if (this.form.invalid) {
this.form.markAllAsTouched();
return;
}
const body = new FormData();
Object.entries(this.form.getRawValue()).forEach(([key, value]) => {
body.append(key, value);
});
this.http.post(FORM_PLUME_ENDPOINT, body).subscribe();
}The full guide
A hosted backend for Angular contact forms.
Wire Reactive Forms or template-driven forms into Form Plume without adding a Node, NestJS, SMTP, or database layer.
- 1Create your Form Plume accountStart free and give your Angular form a hosted endpoint for email, submissions, spam filtering, uploads, integrations, and webhooks.Start free
- 2Create the form and copy the endpointCopy the endpoint URL from Form Plume. It looks like
https://api.formplume.com/f/your-slug - 3Connect it inside AngularCreate a Reactive Form, convert the values to FormData, and submit them with HttpClient. Send one test submission from the real Angular app or preview environment and confirm it reaches Form Plume.
You can use this AI prompt to hand the platform-specific wiring to an assistant without accidentally creating a backend.
Reactive Forms setup
Reactive Forms are a clean default when an Angular contact form needs validation and submission state.
// contact-form.component.ts
import { Component } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { HttpClient } from "@angular/common/http";
const FORM_PLUME_ENDPOINT = "PASTE_YOUR_FORM_PLUME_ENDPOINT_HERE";
@Component({
selector: "app-contact-form",
templateUrl: "./contact-form.component.html",
})
export class ContactFormComponent {
status: "idle" | "sending" | "sent" | "error" = "idle";
form = this.fb.nonNullable.group({
name: ["", Validators.required],
email: ["", [Validators.required, Validators.email]],
message: ["", Validators.required],
});
constructor(
private fb: FormBuilder,
private http: HttpClient,
) {}
submit() {
if (this.form.invalid) {
this.form.markAllAsTouched();
return;
}
this.status = "sending";
const body = new FormData();
Object.entries(this.form.getRawValue()).forEach(([key, value]) => {
body.append(key, value);
});
this.http.post(FORM_PLUME_ENDPOINT, body).subscribe({
next: () => {
this.status = "sent";
this.form.reset();
},
error: () => {
this.status = "error";
},
});
}
}<!-- contact-form.component.html -->
<form [formGroup]="form" (ngSubmit)="submit()">
<label for="name">Name</label>
<input id="name" formControlName="name" autocomplete="name" />
<label for="email">Email</label>
<input id="email" type="email" formControlName="email" autocomplete="email" />
<p *ngIf="form.controls.email.touched && form.controls.email.invalid" role="alert">
Enter a valid email address.
</p>
<label for="message">Message</label>
<textarea id="message" formControlName="message" rows="5"></textarea>
<button type="submit" [disabled]="status === 'sending'">
{{ status === "sending" ? "Sending..." : "Send message" }}
</button>
<p *ngIf="status === 'sent'" role="status">Thanks, we got your message.</p>
<p *ngIf="status === 'error'" role="alert">Please try again.</p>
</form>Form Plume receives the Angular submission and handles the backend pipeline: email notifications, stored submissions, spam filtering, uploads, integrations, and signed webhooks.
File input handling
File inputs are not stored naturally in a text form group. Read the selected file and append it to FormData.
<label for="attachment">Attachment</label>
<input id="attachment" type="file" (change)="selectFile($event)" />selectedFile?: File;
selectFile(event: Event) {
const input = event.target as HTMLInputElement;
this.selectedFile = input.files?.[0];
}
// inside submit()
if (this.selectedFile) {
body.append("attachment", this.selectedFile);
}Do not JSON-stringify files. Let the browser build the multipart request.
Template-driven alternative
For a small Angular form, template-driven forms also work. The important part is that every control has a name, and the final submit sends named fields to Form Plume.
Use Reactive Forms when you need typed values, reusable validators, or more predictable testing.
Angular accessibility notes
Connect errors to fields with aria-describedby when messages are outside the label.
Mark touched controls before showing validation errors so visitors are not greeted by a page full of alerts.
Keep the status message in the template, not only in a toast. Screen reader users need the result near the form.
You do not need a NestJS or Express endpoint just to receive the Angular form. Form Plume is the hosted form backend.
Primary sources
FAQ
Angular form questions
before release.
One line. Zero backend.
