{"id":13483160,"url":"https://github.com/luckyframework/carbon","last_synced_at":"2025-04-05T21:09:59.619Z","repository":{"id":31613222,"uuid":"128421978","full_name":"luckyframework/carbon","owner":"luckyframework","description":"Email library for Crystal. Testable, adapter-based, and catches bugs for you. Comes with an adapter for SendGrid.","archived":false,"fork":false,"pushed_at":"2024-10-20T17:03:29.000Z","size":281,"stargazers_count":85,"open_issues_count":12,"forks_count":18,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-03-29T20:08:25.828Z","etag":null,"topics":["crystal-lang","email-sender","hacktoberfest"],"latest_commit_sha":null,"homepage":"","language":"Crystal","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/luckyframework.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"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}},"created_at":"2018-04-06T16:57:37.000Z","updated_at":"2025-01-28T12:09:04.000Z","dependencies_parsed_at":"2024-01-31T02:03:34.425Z","dependency_job_id":"e1709155-c245-4a06-8a98-72f63994dc8e","html_url":"https://github.com/luckyframework/carbon","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luckyframework%2Fcarbon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luckyframework%2Fcarbon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luckyframework%2Fcarbon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/luckyframework%2Fcarbon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/luckyframework","download_url":"https://codeload.github.com/luckyframework/carbon/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247399885,"owners_count":20932880,"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","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":["crystal-lang","email-sender","hacktoberfest"],"created_at":"2024-07-31T17:01:08.702Z","updated_at":"2025-04-05T21:09:59.600Z","avatar_url":"https://github.com/luckyframework.png","language":"Crystal","funding_links":[],"categories":["Email"],"sub_categories":[],"readme":"# Carbon\n\n[![API Documentation Website](https://img.shields.io/website?down_color=red\u0026down_message=Offline\u0026label=API%20Documentation\u0026up_message=Online\u0026url=https%3A%2F%2Fluckyframework.github.io%2Fcarbon%2F)](https://luckyframework.github.io/carbon)\n\nEmail library written in Crystal.\n\n![code preview](https://user-images.githubusercontent.com/22394/38457909-9f16f9fe-3a64-11e8-852c-74e31238f48b.png)\n\n## Installation\n\nAdd this to your application's `shard.yml`:\n\n```yaml\ndependencies:\n  carbon:\n    github: luckyframework/carbon\n```\n\n## Adapters\n\n- `Carbon::SendGridAdapter`- See [luckyframework/carbon_sendgrid_adapter](https://github.com/luckyframework/carbon_sendgrid_adapter).\n- `Carbon::SmtpAdapter` - See [luckyframework/carbon_smtp_adapter](https://github.com/luckyframework/carbon_smtp_adapter).\n- `Carbon::AwsSesAdapter` - See [keizo3/carbon_aws_ses_adapter](https://github.com/keizo3/carbon_aws_ses_adapter).\n- `Carbon::SendInBlueAdapter` - See [atnos/carbon_send_in_blue_adapter](https://github.com/atnos/carbon_send_in_blue_adapter).\n- `Carbon::MailgunAdapter` - See [atnos/carbon_mailgun_adapter](https://github.com/atnos/carbon_mailgun_adapter).\n- `Carbon::SparkPostAdapter` - See [Swiss-Crystal/carbon_sparkpost_adapter](https://github.com/Swiss-Crystal/carbon_sparkpost_adapter).\n- `Carbon::PostmarkAdapter` - See [makisu/carbon_postmark_adapter](https://github.com/makisu/carbon_postmark_adapter).\n- `Carbon::MailersendAdapter` - See [balakhorvathnorbert/carbon_mailersend_adapter](https://github.com/balakhorvathnorbert/carbon_mailersend_adapter).\n\n## Usage\n\n### First, create a base class for your emails\n\n```crystal\nrequire \"carbon\"\n\n# You can setup defaults in this class\nabstract class BaseEmail \u003c Carbon::Email\n  # For example, set up a default 'from' address\n  from Carbon::Address.new(\"My App Name\", \"support@myapp.com\")\n  # Use a string if you just need the email address\n  from \"support@myapp.com\"\nend\n```\n\n### Configure the mailer class\n\n```crystal\nBaseEmail.configure do |settings|\n  settings.adapter = Carbon::DevAdapter.new(print_emails: true)\nend\n```\n\n### Create a class for your email\n\n```crystal\n# Create an email class\nclass WelcomeEmail \u003c BaseEmail\n  def initialize(@name : String, @email_address : String)\n  end\n\n  to @email_address\n  subject \"Welcome, #{@name}!\"\n  header \"My-Custom-Header\", \"header-value\"\n  reply_to \"no-reply@noreply.com\"\n  # You can also do just `text` or `html` if you don't want both\n  templates text, html\nend\n```\n\n### Create templates\n\nTemplates go in the same folder the email is in:\n\n- Text email: `\u003cfolder_email_class_is_in\u003e/templates/\u003cunderscored_class_name\u003e/text.ecr`\n- HTML email: `\u003cfolder_email_class_is_in\u003e/templates/\u003cunderscored_class_name\u003e/html.ecr`\n\nSo if your email class is in `src/emails/welcome_email.cr`, then your\ntemplates would go in `src/emails/templates/welcome_email/text|html.ecr`.\n\n```\n# in \u003cfolder_of_email_class\u003e/templates/welcome_email/text.ecr\n# Templates have access to instance variables and methods in the email.\nWelcome, \u003c%= @name %\u003e!\n```\n\n```\n# in \u003cfolder_of_email_class\u003e/templates/welcome_email/html.ecr\n\u003ch1\u003eWelcome, \u003c%= @name %\u003e!\u003c/h1\u003e\n```\n\nFor more information on what you can do with Embedded Crystal (ECR), see [the official Crystal documentation](https://crystal-lang.org/api/latest/ECR.html).\n\n### Template layouts\n\nLayouts are optional allowing you to specify how each email template looks individually.\nIf you'd like to have the same layout on each, you can create a layout template in\n`\u003cfolder_email_class_is_in\u003e/templates/\u003clayout_name\u003e/layout.ecr`\n\nIn this file, you'll yield the main email body with `\u003c%= content %\u003e`. Then in your `BaseEmail`, you can specify the name of the layout.\n\n```crystal\nabstract class BaseEmail \u003c Carbon::Email\n  macro inherited\n    from default_from\n    layout :application_layout\n  end\nend\n```\n\n```\n# in src/emails/templates/application_layout/layout.ecr\n\n\u003ch1\u003eOur Email\u003c/h1\u003e\n\n\u003c%= content %\u003e\n\n\u003cdiv\u003efooter\u003c/div\u003e\n```\n\n### Deliver the email\n\n```\n# Send the email right away!\nWelcomeEmail.new(\"Kate\", \"kate@example.com\").deliver\n\n# Send the email in the background using `spawn`\nWelcomeEmail.new(\"Kate\", \"kate@example.com\").deliver_later\n```\n\n### Delay email delivery\n\nThe built-in delay uses the `deliver_later_strategy` setting set to `Carbon::SpawnStrategy`. You can create your own custom delayed strategy\nthat inherits from `Carbon::DeliverLaterStrategy` and defines a `run` method that takes a `Carbon::Email` and a block.\n\nOne example might be a job processor:\n\n```crystal\n# Define your new delayed strategy\nclass SendEmailInJobStrategy \u003c Carbon::DeliverLaterStrategy\n\n  # `block.call` will run `deliver`, but you can call\n  # `deliver` yourself on the `email` when you need.\n  def run(email : Carbon::Email, \u0026block)\n    EmailJob.perform_later(email)\n  end\nend\n\nclass EmailJob \u003c JobProcessor\n  def perform(email : Carbon::Email)\n    email.deliver\n  end\nend\n\n# configure to use your new delayed strategy\nBaseEmail.configure do |settings|\n  settings.deliver_later_strategy = SendEmailInJobStrategy.new\nend\n```\n\n## Testing\n\n### Change the adapter\n\n```crystal\n# In spec/spec_helper.cr or wherever you configure your code\nBaseEmail.configure do\n  # This adapter will capture all emails in memory\n  settings.adapter = Carbon::DevAdapter.new\nend\n```\n\n### Reset emails before each spec and include expectations\n\n```crystal\n# In spec/spec_helper.cr\n\n# This gives you the `be_delivered` expectation\ninclude Carbon::Expectations\n\nSpec.before_each do\n  Carbon::DevAdapter.reset\nend\n```\n\n### Integration testing\n\n```crystal\n# Let's say we have a class that signs the user up and sends the welcome email\n# that was described at the beginning of the README\nclass SignUpUser\n  def initialize(@name : String, @email_address : String)\n  end\n\n  def run\n    sign_user_up\n    WelcomeEmail.new(name: @name, email_address: @email_address).deliver\n  end\nend\n\nit \"sends an email after the user signs up\" do\n  SignUpUser.new(name: \"Emily\", email_address: \"em@gmail.com\").run\n\n  # Test that this email was sent\n  WelcomeEmail.new(name: \"Emily\", email_address: \"em@gmail.com\").should be_delivered\nend\n\n# or we can just check that some emails were sent\nit \"sends some emails\" do\n  SignUpUser.new(name: \"Emily\", email_address: \"em@gmail.com\").run\n\n  Carbon.should have_delivered_emails\nend\n```\n\n### Unit testing\n\nUnit testing is simple. Instantiate your email and test the fields you care about.\n\n```crystal\nit \"builds a nice welcome email\" do\n  email = WelcomeEmail.new(name: \"David\", email_address: \"david@gmail.com\")\n  # Note that recipients are converted to an array of Carbon::Address\n  # So if you use a string value for the `to` field, you'll get an array of\n  # Carbon::Address instead.\n  email.to.should eq [Carbon::Address.new(\"david@gmail.com\")]\n  email.text_body.should contain \"Welcome\"\n  email.html_body.should contain \"Welcome\"\nend\n```\n\n\u003e Note that unit testing can be superfluous in most cases. Instead, try\n\u003e unit testing just fields that have complex logic. The compiler will catch most\n\u003e other issues.\n\n## Development\n\n- `shards install`\n- Make changes\n- `./script/test`\n- `./bin/ameba`\n\n## Contributing\n\n1.  Fork it ( https://github.com/luckyframework/carbon/fork )\n2.  Create your feature branch (git checkout -b my-new-feature)\n3.  Make your changes\n4.  Run `./script/test` to run the specs, build shards, and check formatting\n5.  Commit your changes (git commit -am 'Add some feature')\n6.  Push to the branch (git push origin my-new-feature)\n7.  Create a new Pull Request\n\n## Contributors\n\n- [paulcsmith](https://github.com/paulcsmith) Paul Smith - creator\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluckyframework%2Fcarbon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fluckyframework%2Fcarbon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fluckyframework%2Fcarbon/lists"}