{"id":13394995,"url":"https://github.com/heartcombo/mail_form","last_synced_at":"2025-12-17T05:47:00.326Z","repository":{"id":767112,"uuid":"447962","full_name":"heartcombo/mail_form","owner":"heartcombo","description":"Send e-mail straight from forms in Rails with I18n, validations, attachments and request information.","archived":false,"fork":false,"pushed_at":"2025-10-29T11:52:02.000Z","size":224,"stargazers_count":875,"open_issues_count":0,"forks_count":65,"subscribers_count":16,"default_branch":"main","last_synced_at":"2025-11-27T14:50:33.082Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","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/heartcombo.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"MIT-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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2009-12-24T11:20:17.000Z","updated_at":"2025-11-13T10:18:43.000Z","dependencies_parsed_at":"2024-04-09T19:25:21.840Z","dependency_job_id":"92774e9d-0104-4391-9b56-8b038aac1dcd","html_url":"https://github.com/heartcombo/mail_form","commit_stats":{"total_commits":180,"total_committers":29,"mean_commits":6.206896551724138,"dds":0.6444444444444444,"last_synced_commit":"b1799408737690422ec80ee7d5b420adbc041548"},"previous_names":["plataformatec/mail_form"],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/heartcombo/mail_form","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heartcombo%2Fmail_form","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heartcombo%2Fmail_form/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heartcombo%2Fmail_form/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heartcombo%2Fmail_form/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/heartcombo","download_url":"https://codeload.github.com/heartcombo/mail_form/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/heartcombo%2Fmail_form/sbom","scorecard":{"id":459128,"data":{"date":"2025-08-11","repo":{"name":"github.com/heartcombo/mail_form","commit":"90881203eaed4f688d70ea34e6e8c5243e949b97"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.5,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":0,"reason":"Found 2/23 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:60: update your workflow using https://app.stepsecurity.io/secureworkflow/heartcombo/mail_form/test.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yml:61: update your workflow using https://app.stepsecurity.io/secureworkflow/heartcombo/mail_form/test.yml/main?enable=pin","Info:   0 out of   1 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: MIT-LICENSE:0","Info: FSF or OSI recognized license: MIT License: MIT-LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 9 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"22 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-h47h-mwp9-c6q6","Warn: Project is vulnerable to: GHSA-fwhr-88qx-h9g7","Warn: Project is vulnerable to: GHSA-vfg9-r3fq-jvx4","Warn: Project is vulnerable to: GHSA-vfm5-rmrh-j26v","Warn: Project is vulnerable to: GHSA-x76w-6vjr-8xgj","Warn: Project is vulnerable to: GHSA-7fc5-f82f-cx69","Warn: Project is vulnerable to: GHSA-j3g3-5qv5-52mj","Warn: Project is vulnerable to: GHSA-353f-x4gh-cqq8","Warn: Project is vulnerable to: GHSA-5w6v-399v-w3cc","Warn: Project is vulnerable to: GHSA-mrxw-mxhj-p664","Warn: Project is vulnerable to: GHSA-r95h-9x8f-r3f7","Warn: Project is vulnerable to: GHSA-vvfq-8hwr-qm4m","Warn: Project is vulnerable to: GHSA-7g2v-jj9q-g3rg","Warn: Project is vulnerable to: GHSA-7wqh-767x-r66v","Warn: Project is vulnerable to: GHSA-8cgq-6mh2-7j6v","Warn: Project is vulnerable to: GHSA-gjh7-p2fx-99vx","Warn: Project is vulnerable to: GHSA-9j94-67jr-4cqj","Warn: Project is vulnerable to: GHSA-2x5m-9ch4-qgrr","Warn: Project is vulnerable to: GHSA-638j-pmjw-jq48","Warn: Project is vulnerable to: GHSA-cfjx-w229-hgx5","Warn: Project is vulnerable to: GHSA-rxv5-gxqc-xx8g","Warn: Project is vulnerable to: GHSA-w8gc-x259-rc7x"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-19T10:37:32.162Z","repository_id":767112,"created_at":"2025-08-19T10:37:32.162Z","updated_at":"2025-08-19T10:37:32.162Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27759615,"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-12-16T02:00:10.477Z","response_time":57,"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":[],"created_at":"2024-07-30T17:01:38.617Z","updated_at":"2025-12-17T05:47:00.320Z","avatar_url":"https://github.com/heartcombo.png","language":"Ruby","readme":"## MailForm\n\n[![Gem Version](https://fury-badge.herokuapp.com/rb/mail_form.svg)](http://badge.fury.io/rb/mail_form)\n\n### Rails 7+\n\nThis gem was built on top of `ActiveModel` to showcase how you can pull in validations, naming\nand `i18n` from Rails to your models without the need to implement it all by yourself.\n\nThis README refers to the **MailForm** gem to be used in Rails 7+. For instructions\non how to use MailForm in older versions of Rails, please refer to the available branches.\n\n### Description\n\n**MailForm** allows you to send an e-mail straight from a form. For instance,\nif you want to make a contact form just the following lines are needed (including the e-mail):\n\n```ruby\nclass ContactForm \u003c MailForm::Base\n  attribute :name, validate: true\n  attribute :email, validate: /\\A[^@\\s]+@[^@\\s]+\\z/i\n  attribute :file, attachment: true\n\n  attribute :message\n  attribute :nickname, captcha: true\n\n  # Declare the e-mail headers. It accepts anything the mail method\n  # in ActionMailer accepts.\n  def headers\n    {\n      subject: \"My Contact Form\",\n      to: \"your.email@your.domain.com\",\n      from: %(\"#{name}\" \u003c#{email}\u003e)\n    }\n  end\nend\n```\n\nThen you start a console with `rails console` and type:\n\n```ruby\n\u003e\u003e c = ContactForm.new(name: 'José', email: 'jose@email.com', message: 'Cool!')\n\u003e\u003e c.deliver\n```\n\nCheck your inbox and the e-mail will be there, with the sent fields (assuming that\nyou configured your mailer delivery method properly).\n\n### MailForm::Base\n\nWhen you inherit from `MailForm::Base`, it pulls down a set of stuff from `ActiveModel`,\nas `ActiveModel::Validation`, `ActiveModel::Translation` and `ActiveModel::Naming`.\n\nThis brings `I18n`, error messages, validations and attributes handling like in\n`ActiveRecord` to **MailForm**, so **MailForm** can be used in your controllers and form builders without extra tweaks. This also means that instead of the following:\n\n```ruby\nattribute :email, validate: /\\A[^@\\s]+@[^@\\s]+\\z/i\n```\n\nYou could actually do this:\n\n```ruby\nattribute :email\nvalidates_format_of :email, with: /\\A[^@\\s]+@[^@\\s]+\\z/i\n```\n\nChoose the one which pleases you the most. For more information on the API, please\ncontinue reading below.\n\n### Playing together ORMs\n\n**MailForm** plays nice with ORMs as well. You just need to include `MailForm::Delivery`\nin your model and declare which attributes should be sent:\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  include MailForm::Delivery\n\n  append :remote_ip, :user_agent, :session\n  attributes :name, :email, :created_at\n\n  def headers\n    {\n      to: \"your.email@your.domain.com\",\n      subject: \"User created an account\"\n    }\n  end\nend\n```\n\nThe delivery will be triggered in an `after_create` hook.\n\n## Installation\n\nInstall **MailForm** is very easy. Just edit your Gemfile adding the following:\n\n```ruby\ngem 'mail_form'\n```\n\nThen run `bundle install` to install **MailForm**.\n\nYou can run `rails generate mail_form` to view help information on how to generate\na basic form to get you started.\n\n## API Overview\n\n### attributes(*attributes)\n\nDeclare your form attributes. All attributes declared here will be appended\nto the e-mail, except the ones :captcha is true.\n\nOptions:\n\n* `:validate` - A hook to `validates_*_of`. When `true` is given, validates the\n  presence of the attribute. When a regexp, validates format. When array,\n  validates the inclusion of the attribute in the array.\n\n  Whenever `:validate` is given, the presence is automatically checked. Give\n  `allow_blank: true` to override.\n\n  Finally, when `:validate` is a symbol, the method given as symbol will be\n  called. Then you can add validations as you do in Active Record (`errors.add`).\n\n* `:attachment` - When given, expects a file to be sent and attaches\n  it to the e-mail. Don't forget to set your form to multitype.\n  It also accepts multiple files through a single attachment attribute,\n  and will attach them individually to the e-mail.\n\n* `:captcha` - When true, validates the attributes must be blank.\n  This is a simple way to avoid spam and the input should be hidden with CSS.\n\nExamples:\n\n```ruby\nclass ContactForm \u003c MailForm::Base\n  attributes :name, validate: true\n  attributes :email, validate: /\\A[^@\\s]+@[^@\\s]+\\z/i\n  attributes :type, validate: [\"General\", \"Interface bug\"]\n  attributes :message\n  attributes :screenshot, attachment: true, validate: :interface_bug?\n  attributes :nickname, captcha: true\n\n  def interface_bug?\n    if type == 'Interface bug' \u0026\u0026 screenshot.nil?\n      self.errors.add(:screenshot, \"can't be blank on interface bugs\")\n    end\n  end\nend\n\nc = ContactForm.new(nickname: 'not_blank', email: 'your@email.com', name: 'José')\nc.valid?   #=\u003e true\nc.spam?    #=\u003e true  (raises an error in development, to remember you to hide it)\nc.deliver  #=\u003e false (just delivers if is not a spam and is valid, raises an error in development)\n\nc = ContactForm.new(email: 'invalid')\nc.valid?               #=\u003e false\nc.errors.inspect       #=\u003e { name: :blank, email: :invalid }\nc.errors.full_messages #=\u003e [ \"Name can't be blank\", \"Email is invalid\" ]\n\nc = ContactForm.new(name: 'José', email: 'your@email.com')\nc.deliver\n```\n\n### append(*methods)\n\n**MailForm** also makes easy to append request information from client to the sent\nmail. You just have to do:\n\n```ruby\nclass ContactForm \u003c MailForm::Base\n  append :remote_ip, :user_agent, :session\n  # ...\nend\n```\n\nAnd in your controller:\n\n```ruby\n@contact_form = ContactForm.new(params[:contact_form])\n@contact_form.request = request\n```\n\nThe remote ip, user agent and session will be sent in the e-mail in a\nrequest information session. You can give to append any method that the\nrequest object responds to.\n\n## I18n\n\nI18n in **MailForm** works like in ActiveRecord, so all models, attributes and messages\ncan be used with localized. Below is an I18n file example file:\n\n```ruby\nmail_form:\n  models:\n    contact_form: \"Your site contact form\"\n  attributes:\n    contact_form:\n      email: \"E-mail\"\n      telephone: \"Telephone number\"\n      message: \"Sent message\"\n  request:\n    title: \"Technical information about the user\"\n    remote_ip: \"IP Address\"\n    user_agent: \"Browser\"\n```\n\n## Custom e-mail template\n\nTo customize the e-mail template that is used create a file called `contact.erb` in `app/views/mail_form`.\nTake a look at `lib/mail_form/views/mail_form/contact.erb` in this repo to see how the default template works.\n\n## Supported Ruby / Rails versions\n\nWe intend to maintain support for all Ruby / Rails versions that haven't reached end-of-life.\n\nFor more information about specific versions please check [Ruby](https://www.ruby-lang.org/en/downloads/branches/)\nand [Rails](https://guides.rubyonrails.org/maintenance_policy.html) maintenance policies, and our test matrix.\n\n## Bugs and Feedback\n\nIf you discover any bug, please use github issues tracker.\n\n## License\n\nMIT License.\nCopyright 2009-2019 Plataformatec.\nCopyright 2020-2025 Rafael França, Carlos Antonio da Silva.\n","funding_links":[],"categories":["Ruby","Email"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheartcombo%2Fmail_form","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fheartcombo%2Fmail_form","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fheartcombo%2Fmail_form/lists"}