Skip to content
MH

Legal

Privacy Policy

What this site collects, why it collects it, where it goes, and the things it deliberately does not do. Every statement below describes what the application actually implements.

Last revised 28 August 2026

Who runs this site

This website is the personal academic and professional portfolio of Md. Mehedi Hasan. It is run by him as an individual — not by a company, an employer or a laboratory — and it is administered from a single account. There is no team, no data-processing department and no third party acting on his behalf.

In short

Contact form
The name, email address, optional subject and message you type. Stored in this site’s database, and emailed to the owner.
Page views
The path visited, a salted one-way hash, the referring site’s hostname, a two-letter country and a coarse device family. No IP address is stored.
Cookies
None, for a visitor who never signs in. There is no visitor account system, and so no consent banner.
Advertising
None. No ad network, no tag manager, no third-party analytics script, no social or chat widget.
Sale of data
Never. Nothing collected here is sold, rented, or shared for marketing.

The contact form

The form on the contact page collects four things, and they are the four things you can see:

  • Your name — 1 to 120 characters.
  • Your email address — 3 to 254 characters, checked for a single @ and a dot in the domain.
  • A subject — optional, up to 200 characters. Left blank, it is stored as nothing rather than as an empty string.
  • Your message — 1 to 5,000 characters.

They are collected so the message can be read and answered. The email address is the only route a reply can take; without one there is no way to respond.

What the stored row contains is those four fields, a status flag the owner uses to mark a message as read or replied, and the times the row was created and last changed. That is the whole row. Your IP address is not stored with the message, and neither is your browser’s user-agent string.

The form carries one hidden field that a person never sees and an indiscriminate bot fills in. If it arrives filled, the submission is discarded and the page still reports success — telling a script it was detected only teaches it to try again. Submissions are also limited to three per email address per ten minutes.

You cannot read your own message back afterwards. The database grants an anonymous visitor permission to insert into that table and nothing else, so there is no page and no endpoint that will return it.

The emailed copy

When the notification credentials are configured, a copy of a new message is emailed to the owner’s own mailbox so a submission is noticed without opening the admin area. The email carries the same four fields plus the UTC time of submission, and your address is placed in the Reply-To header so a reply goes to you rather than to the site.

That mailbox is a Gmail account, which means the content of your message is transmitted to Google and stored there. Writing to the published email address instead reaches the same mailbox, so it is the same destination by a different road — the difference is only that the web form also keeps a copy in this site’s own database.

The message is written to the database before the email is attempted. A failure to send never fails your submission, never shows you an error and never loses the message; it is recorded in the server log and nothing else. If the credentials are absent, the email step is skipped and everything else behaves identically.

Google API Services and Gmail data

The emailed copy is sent through the Gmail API. This application holds one OAuth 2.0 authorization, for one Google account — the owner’s — and that authorization carries exactly one scope: https://www.googleapis.com/auth/gmail.send.

That scope permits sending a message and nothing else. This application cannot read, search, list, label, modify or delete mail, and it cannot even ask Google which address it was authorized for — which is why the destination address is configured as a separate value rather than discovered.

No visitor is ever asked to sign in with Google, and this site never requests access to a visitor’s Google account. There is no “Sign in with Google” button anywhere on it. The only Google user data involved anywhere in this application is the owner’s own.

Use of information received from Google APIs adheres to the Google API Services User Data Policy (opens in a new tab), including the Limited Use requirements. Concretely: the authorization is used only to send the owner a notification about a message submitted through this site’s contact form; it is not transferred to anyone else except as needed to provide that one feature; it is not used for advertising; it is not used to train or improve any model; and no human other than the owner reads it.

The refresh token that carries this authorization is a server-side secret. It is never sent to a browser, never embedded in a page, and never written to a log. It can be revoked at any time from the owner’s Google account permissions, which ends the email notification and changes nothing else.

Counting page views

Visits to public pages are counted by this application itself. No third-party analytics service is involved, and no analytics script runs in your browser — the count happens on the server as the page is served. Each counted view records:

  • The path visited, without the query string.
  • A visitor hash, described below.
  • The hostname of the referring site, if there was one and it was not this site. The hostname only — never the full referring URL.
  • A two-letter country code, as reported by the hosting network.
  • A coarse device family.
  • The time.

There is no cookie and no stored IP address. The visitor hash is a SHA-256 of a secret salt, the current UTC year and month, and the requesting address, truncated to 32 hexadecimal characters — and the database enforces that shape as a constraint, so an address cannot end up in that column even by mistake. Because the month is part of the input, the hash for the same reader changes on the first of every month, which is what stops it accumulating into a long-term identifier.

This is deliberately not described as anonymous in the mathematical sense. Anyone holding both the salt and the table could test a candidate address against a hash. The salt is a server-side secret, is not in the source code, and if it is unset nothing is recorded at all.

Requests to the admin area, the API routes, framework internals, the login page and file paths are not counted. The figures are only ever read as aggregates — counts by day, path, country, device and referrer. There is no per-visitor view, because there is nothing in the table that would build one.

Cookies and browser storage

A visitor who reads this site and never signs in receives no cookie from it. There are exactly two cookies in the whole application, and neither is set for a reader:

  • The admin session. Set by the authentication library when the owner signs in to the admin area; the names begin sb-. There is no public sign-up and no visitor login, so nobody else can cause one to be set.
  • A one-time authorization nonce. Used once during the Gmail setup described above to prove the response came back from the request that started it. Scoped to the Gmail API path, marked HttpOnly, and expires after ten minutes.

Your light, dark or system theme choice is kept in localStorage under the key mmh-theme. That is browser storage and not a cookie: it stays on your device, is never attached to a request, and is never seen by the server. Clearing site data removes it.

There is no consent banner, because there is nothing here to consent to.

Services this site depends on

  • Supabase (opens in a new tab) — the PostgreSQL database and the authentication for the single admin account. Contact messages and page-view rows are stored here.
  • Google (opens in a new tab) — receives the content of a contact message as the emailed copy described above, over the send-only scope and no other.
  • Vercel (opens in a new tab) — hosting. Like any host, it processes the requests that make up your visit, including your IP address, in order to serve the page, and it reports a country code to the application.
  • DuckDuckGo’s icon service (opens in a new tab) — the one third party your browser itself contacts. Most organisation marks on this site are files served from this domain, but where no bundled mark exists the page falls back to a favicon fetched from icons.duckduckgo.com, which means that request tells that service which organisation’s domain appears on the page you are reading. It is sent with a no-referrer policy, so it does not carry the page URL, and it degrades to a plain monogram when no icon exists.

Two things that are commonly third-party and are not here. Fonts are self-hosted, compiled into the deployment at build time, so no request is made to a font CDN. And there is no third-party JavaScript of any kind — no analytics SDK, no tag manager, no ad network, no social embed, no chat widget. Images stored on other hosts are fetched by this server from an explicit allow-list of hostnames rather than by your browser.

What actually protects this

Listed rather than summarised, because “your privacy is important to us” is not a control:

  • Row-level security is enabled on every table. Contact messages, page views, private profile fields and uploaded media have no public read policy at all — and no policy means no rows, for anyone who is not the admin.
  • A second, independent restriction at the database grant layer: an anonymous visitor holds read permission on published content, the ability to insert a contact message and a page view, and nothing more.
  • The running application never holds a service-role key, so no code path in it can bypass either of the two layers above.
  • Every administrative action is checked three times independently — in middleware, again in the server code that performs it, and again by the database.
  • HTTPS, with HTTP Strict Transport Security on the production deployment.
  • Response headers that disable MIME sniffing, deny framing outright, withhold the path from cross-origin referrers, switch off camera, microphone and geolocation, and declare a content security policy.
  • Author-written HTML is sanitised on the server before it is stored, against an allow-list of tags and CSS properties.

What is not claimed: no certification, no third-party audit, no penetration-test result, no encryption-at-rest claim beyond whatever the hosting providers do by default, no breach-notification procedure, and no guarantee. This is a personal site, defended carefully, and it says so rather than dressing itself up as an enterprise.

How long things are kept

Plainly: there is no automated deletion, and naming a retention period here would be inventing one.

  • Contact messages are kept until the owner deletes one by hand in the admin area. That deletion is immediate and cannot be undone.
  • Page-view rows are kept indefinitely. A pruning routine exists in the database, but nothing schedules it — it has to be run deliberately.

If a fixed retention period is introduced later, this section changes with it and the revision date at the top moves.

Your choices, and one thing that cannot be done

Ask for a copy of the message you sent, or ask for it to be deleted. Use the contact form or the published email address, writing from the address you originally used, so the right row can be found. It is done by hand, because that is the only mechanism that exists.

Depending on where you live you may have statutory rights over your data — access, correction, erasure, objection, portability. The practical route to all of them here is the same: ask, and it is acted on manually.

The one request that cannot be fulfilled is deletion of page-view rows. Nothing in that table links a row to a person. There is no reversible identifier, so there is no way to establish which rows are yours — and that is a direct consequence of not collecting the identifier in the first place. It is a limitation, and it is stated as one rather than quietly omitted.

Children

This is a research and professional portfolio. It is not directed at children, it has no account system, and nothing on it is designed to collect information from a child.

Changes to this policy

This page changes when the site changes — which is the point of writing it against the code rather than against a template. The date at the top is the last revision. No version history is published; a substantive change moves that date.

Reaching the owner

Use the contact form or the email address published on that page. The companion document to this one is the terms of service.