{"id":30676248,"url":"https://github.com/elliot40404/mailc","last_synced_at":"2025-09-01T09:43:37.852Z","repository":{"id":309271085,"uuid":"1035221396","full_name":"elliot40404/mailc","owner":"elliot40404","description":"Type-safe compile-time email templates inspired by sqlc. Generate strongly-typed Go functions from HTML templates and catch errors at build time, not in production.","archived":false,"fork":false,"pushed_at":"2025-08-10T23:06:36.000Z","size":25,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-09-01T06:54:16.294Z","etag":null,"topics":["build-tool","code-generation","compile-time","developer-tools","email","golang","golang-library","html-templates","sqlc","templates","type-safety"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/elliot40404.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":null,"patreon":null,"open_collective":null,"ko_fi":"elliot40404","tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"lfx_crowdfunding":null,"polar":null,"buy_me_a_coffee":null,"thanks_dev":null,"custom":null}},"created_at":"2025-08-09T23:10:39.000Z","updated_at":"2025-08-10T23:06:39.000Z","dependencies_parsed_at":"2025-08-11T00:00:52.551Z","dependency_job_id":"ea490727-adcb-49b1-9aa3-c91c62f63871","html_url":"https://github.com/elliot40404/mailc","commit_stats":null,"previous_names":["elliot40404/mailc"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/elliot40404/mailc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elliot40404%2Fmailc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elliot40404%2Fmailc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elliot40404%2Fmailc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elliot40404%2Fmailc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elliot40404","download_url":"https://codeload.github.com/elliot40404/mailc/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elliot40404%2Fmailc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273100880,"owners_count":25045700,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-01T02:00:09.058Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["build-tool","code-generation","compile-time","developer-tools","email","golang","golang-library","html-templates","sqlc","templates","type-safety"],"created_at":"2025-09-01T09:43:37.185Z","updated_at":"2025-09-01T09:43:37.827Z","avatar_url":"https://github.com/elliot40404.png","language":"Go","funding_links":["https://ko-fi.com/elliot40404"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n## mailc – Type‑safe HTML Email Template Compiler for Go\n\nGenerate strongly‑typed Go functions from plain HTML email templates. Render subject and body with Go templates, avoid runtime surprises, and keep templates easy to edit.\n\n\u003c/div\u003e\n\n---\n\n## Features\n\n- **Type‑safe data models** from annotations in `.html`\n- **Single variables supported**: `{{var}}` inferred as `string` if no type hint\n- **Optional subject**: functions return `{Subject, HTML}, error`; empty Subject when not provided\n- **Normalized identifiers**: `{{User.Name}}` or `{{ .User.Name}}` both work\n- **Per‑template types** to avoid collisions across templates\n- **Conditional imports**: `text/template` only when subject exists; `time` when `time.Time` used\n- **No runtime file I/O**: templates compile to Go code in your repo\n\n---\n\n## Install\n\n```bash\ngo install github.com/elliot40404/mailc/cmd/mailc@latest\n```\n\nOr use the included Just recipes (Windows/macOS/Linux) to build and run via `./bin/mailc`.\n\n---\n\n## How it works (Overview)\n\nPlace HTML templates in a directory. Annotate your data model using special HTML comments:\n\n- Subject: `\u003c!-- $Subject: Welcome {{User.Name}} --\u003e`\n- Struct: `\u003c!-- @type User --\u003e`\n- Field: `\u003c!-- @type User.Name string --\u003e`\n- Top‑level variable: `\u003c!-- @type inviteLink string --\u003e`\n\nDuring generation, mailc:\n\n- Parses subject and body\n- Detects `@type` declarations for structs and fields\n- Infers undeclared simple variables like `{{username}}` as `string`\n- Normalizes top‑level references to `{{ .Field}}`\n- Emits a function `NameEmail(*NameEmailData) (NameEmailResult, error)` in `package emails`\n\n---\n\n## Quickstart\n\n1) In your app, create templates under `./emails/` (or `./templates/`). Example `./emails/order_confirmation.html`:\n\n```html\n\u003c!-- $Subject: Welcome {{User.Name}} – Order #{{Order.ID}} placed {{Order.CreatedAt}} --\u003e\n\n\u003c!-- @type Order --\u003e\n\u003c!-- @type Order.ID int --\u003e\n\u003c!-- @type Order.Name string --\u003e\n\u003c!-- @type Order.Qty int --\u003e\n\u003c!-- @type Order.CreatedAt time.Time --\u003e\n\n\u003c!-- @type User --\u003e\n\u003c!-- @type User.Name string --\u003e\n\n\u003chtml\u003e\n  \u003cbody\u003e\n    \u003ch1\u003eWelcome, {{User.Name}}!\u003c/h1\u003e\n    \u003cp\u003eYour order \u003cstrong\u003e#{{Order.ID}}\u003c/strong\u003e for {{Order.Name}} (x{{Order.Qty}}) was placed at {{Order.CreatedAt}}.\u003c/p\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\n2) Generate code into your project’s `internal/emails`:\n\n```bash\nmailc generate -input ./emails -output ./internal/emails\n# Or with custom package name:\nmailc generate -input ./emails -output ./internal/emails -package myemails\n```\n\n3) Use the generated package in your code:\n\n```go\nimport (\n  \"log\"\n  \"time\"\n  emails \"your/module/internal/emails\"\n)\n\nfunc send() error {\n  data := \u0026emails.OrderConfirmationEmailData{\n    User:  emails.OrderConfirmationEmailUser{Name: \"Jane\"},\n    Order: emails.OrderConfirmationEmailOrder{ID: 42, Name: \"Widget\", Qty: 3, CreatedAt: time.Now()},\n  }\n  res, err := emails.OrderConfirmationEmail(data)\n  if err != nil { return err }\n  // res.Subject and res.HTML now contain your email subject/body\n  return nil\n}\n```\n\n---\n\n## Examples\n\nThis repository also ships example templates under `examples/templates/`. You can compile them to `examples/generated/` with:\n\n```bash\njust gen-examples\n```\n\nSuggested template names (any filename is supported; names are safely converted to exported Go identifiers):\n\n- `welcome_personalized.html` – uses inferred variables like `{{username}}`, `{{firstName}}`\n- `account_invite_link.html` – uses a typed top‑level variable `\u003c!-- @type inviteLink string --\u003e`\n- `order_confirmation.html` – demonstrates multiple structs and fields\n- `welcome_no_subject.html` – no subject block; result `Subject` will be empty\n\n---\n\n## Generated API\n\nFor each `name.html`, mailc generates in `package emails`:\n\n- `type NameEmailData struct { ... }` – root input data\n- `type RenderedEmail struct { Subject string; HTML string }` – shared output type (in `types.go`)\n- Struct types per template, e.g. `NameEmailUser`, `NameEmailOrder`\n- `func NameEmail(data *NameEmailData) (RenderedEmail, error)` – renders subject and HTML\n\nConstant names are unique per file, e.g. `nameEmailHTMLTemplate` and `nameEmailSubjectTemplate`.\n\n---\n\n## Template syntax and annotations\n\n- **Subject (optional)**: `\u003c!-- $Subject: ... --\u003e`\n  - If omitted, `Result.Subject` is empty and `text/template` is not imported\n- **Top‑level variables**:\n  - With hint: `\u003c!-- @type apiKey string --\u003e` → field `APIKey string`\n  - Without hint: `{{username}}` or `{{firstName}}` → inferred as `string`\n  - Inference recognizes names matching `[A-Za-z][A-Za-z0-9_]*`\n- **Structs and fields**:\n  - `\u003c!-- @type User --\u003e`, `\u003c!-- @type User.Name string --\u003e`\n  - Use Go types (primitives or qualified like `time.Time`)\n- **Normalization**:\n  - `{{User.Name}}` or `{{ .User.Name}}` both work\n  - Top‑level references are normalized to `{{ .Field}}`\n\n---\n\n## File naming guidelines\n\n- Any filename is supported; mailc converts filenames into exported identifiers safely\n- Examples:\n  - `order_confirmation.html` → `OrderConfirmationEmail`/`OrderConfirmationEmailData`\n  - `account-invite-link.html` → `AccountInviteLinkEmail`/...\n- Prefer readable names; underscores or hyphens are fine\n\n---\n\n## Watching for changes (live compile)\n\n`mailc` is a one-shot code generator. To recompile on file changes, wrap it with a watcher such as:\n\n- `watchexec --exts html -- mailc generate -input ./emails -output ./internal/emails`\n- `watchman-make -p 'emails/*.html' -r 'mailc generate -input ./emails -output ./internal/emails'`\n- `nodemon --ext html --exec \"mailc generate -input ./emails -output ./internal/emails\"`\n\nAdd this to your dev workflow so changes to templates automatically regenerate code.\n\n---\n\n## Demo app (from this repo’s examples)\n\nWe include a tiny demo that renders one of the example templates and shows how to send using `net/smtp`.\n\nSteps:\n\n```bash\njust gen-examples           # builds local mailc and generates examples\ngo run ./examples/cmd/demo  # renders and attempts to send (requires SMTP env vars)\n```\n\nEnv vars used by the demo:\n\n- `SMTP_HOST` (e.g. smtp.gmail.com)\n- `SMTP_PORT` (e.g. 587)\n- `SMTP_USER`\n- `SMTP_PASS`\n- `SMTP_FROM`\n- `SMTP_TO`\n\nNote: Use an app password or a test SMTP server for local development.\n\n---\n\n## CLI\n\n```text\nmailc - Type-safe email templates\n\nUsage:\n  mailc [command] [flags]\n\nCommands:\n  generate   Parse HTML templates and generate Go code\n  help       Show help\n  version    Show current mailc version\n\nFlags (for generate):\n  -input     Directory containing HTML email templates (default: ./emails)\n  -output    Directory to write generated Go code (default: ./internal/emails)\n  -package   Package name for generated Go code (default: emails)\n```\n\nJust recipes:\n\n- `just build` – build the CLI to `./bin/mailc`\n- `just gen-examples` – generate `examples/templates` → `examples/generated`\n- `just test` / `just testv` – run tests\n- `just lint-fix` – auto-fix lint issues\n\n---\n\n## Do’s and Don’ts\n\n- Do use `@type` to declare structs and fields you reference\n- Do rely on simple variable inference for `{{var}}` when you want `string`\n- Do use Go types in hints (e.g. `int`, `string`, `time.Time`)\n- Don’t expect inference for complex expressions (e.g. `{{ printf ... }}`) or dotted paths\n- Don’t put secrets in templates; mailc compiles templates into your binary\n\n---\n\n## Troubleshooting\n\n- Missing variable/field at render time\n  - Re‑run generation after template changes\n  - Ensure your variable names match `[A-Za-z][A-Za-z0-9_]*`\n\n- Unused import `text/template`\n  - Happens only if a subject was present but later removed; re‑generate\n\n- Importing the generated package\n  - Generated code defaults to `package emails`. Import it by the location you generated into\n\n---\n\n## Contributing\n\n- Run `just lint-fix` and `just test` before pushing\n- Example templates: add under `examples/templates/` and run `just gen-examples`\n- Install git hooks locally so you don’t push broken code:\n\n```bash\ngit config core.hooksPath .githooks\nchmod +x .githooks/*\n```\n\n---\n\n## License\n\nMIT License. See `LICENSE` for details.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felliot40404%2Fmailc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felliot40404%2Fmailc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felliot40404%2Fmailc/lists"}