├── .github └── workflows │ └── build.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── docker-compose.yml ├── examples ├── api_keys.rb ├── audiences.rb ├── batch_email.rb ├── broadcasts.rb ├── contacts.rb ├── domains.rb ├── schedule_email.rb ├── simple_mail.rb ├── with_attachments.rb └── with_b64_attachments.rb ├── lib ├── resend.rb └── resend │ ├── api_keys.rb │ ├── audiences.rb │ ├── batch.rb │ ├── broadcasts.rb │ ├── client.rb │ ├── contacts.rb │ ├── domains.rb │ ├── emails.rb │ ├── errors.rb │ ├── mailer.rb │ ├── railtie.rb │ ├── request.rb │ └── version.rb ├── renovate.json ├── resend.gemspec ├── resources └── invoice.pdf ├── sig └── resend.rbs └── spec ├── api_keys_spec.rb ├── audiences_spec.rb ├── batch_spec.rb ├── broadcasts_spec.rb ├── contacts_spec.rb ├── domains_spec.rb ├── emails_spec.rb ├── railtie └── mailer_spec.rb ├── request_spec.rb ├── resend_spec.rb └── spec_helper.rb /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: ["push", "pull_request"] 4 | 5 | jobs: 6 | test: 7 | name: Ruby ${{ matrix.ruby }} 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false # don't fail all matrix builds if one fails 11 | matrix: 12 | ruby: 13 | - "2.7" 14 | - "3.0" 15 | - "3.1" 16 | continue-on-error: ${{ endsWith(matrix.ruby, 'head') }} 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: ruby/setup-ruby@v1 20 | with: 21 | ruby-version: ${{ matrix.ruby }} 22 | - name: Install dependencies 23 | run: bundle install 24 | - name: Rubocop 25 | run: bundle exec rubocop 26 | - name: Run tests 27 | run: bundle exec rake spec 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | /sig/ 10 | 11 | # rspec failure tracking 12 | .rspec_status 13 | 14 | test.rb 15 | *.gem 16 | .vscode -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | TargetRubyVersion: 2.6 3 | SuggestExtensions: false 4 | NewCops: disable 5 | Exclude: 6 | - "./spec/**/**" 7 | - "./examples/**" 8 | 9 | Style/GuardClause: 10 | Enabled: false 11 | 12 | Style/StringLiterals: 13 | Enabled: true 14 | EnforcedStyle: double_quotes 15 | 16 | Style/StringLiteralsInInterpolation: 17 | Enabled: true 18 | EnforcedStyle: double_quotes 19 | 20 | Layout/LineLength: 21 | Max: 120 22 | 23 | Metrics/ClassLength: 24 | Max: 169 25 | 26 | Metrics/MethodLength: 27 | Max: 14 28 | 29 | Metrics/ParameterLists: 30 | MaxOptionalParameters: 4 31 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.4.3 2 | RUN apt-get update -qq && apt-get install -y build-essential libpq-dev 3 | 4 | RUN mkdir -p /app 5 | 6 | WORKDIR /app 7 | 8 | ADD . /app/ 9 | 10 | RUN bundle install -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # Specify your gem's dependencies in resend.gemspec 6 | gemspec 7 | 8 | gem "rake", "~> 13.0" 9 | 10 | gem "rspec", "~> 3.0" 11 | 12 | gem "rubocop", "~> 1.21" 13 | 14 | gem "httparty", "~> 0.21.0" 15 | 16 | gem "pry-byebug" 17 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | resend (0.22.0) 5 | httparty (>= 0.21.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actioncable (7.0.4) 11 | actionpack (= 7.0.4) 12 | activesupport (= 7.0.4) 13 | nio4r (~> 2.0) 14 | websocket-driver (>= 0.6.1) 15 | actionmailbox (7.0.4) 16 | actionpack (= 7.0.4) 17 | activejob (= 7.0.4) 18 | activerecord (= 7.0.4) 19 | activestorage (= 7.0.4) 20 | activesupport (= 7.0.4) 21 | mail (>= 2.7.1) 22 | net-imap 23 | net-pop 24 | net-smtp 25 | actionmailer (7.0.4) 26 | actionpack (= 7.0.4) 27 | actionview (= 7.0.4) 28 | activejob (= 7.0.4) 29 | activesupport (= 7.0.4) 30 | mail (~> 2.5, >= 2.5.4) 31 | net-imap 32 | net-pop 33 | net-smtp 34 | rails-dom-testing (~> 2.0) 35 | actionpack (7.0.4) 36 | actionview (= 7.0.4) 37 | activesupport (= 7.0.4) 38 | rack (~> 2.0, >= 2.2.0) 39 | rack-test (>= 0.6.3) 40 | rails-dom-testing (~> 2.0) 41 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 42 | actiontext (7.0.4) 43 | actionpack (= 7.0.4) 44 | activerecord (= 7.0.4) 45 | activestorage (= 7.0.4) 46 | activesupport (= 7.0.4) 47 | globalid (>= 0.6.0) 48 | nokogiri (>= 1.8.5) 49 | actionview (7.0.4) 50 | activesupport (= 7.0.4) 51 | builder (~> 3.1) 52 | erubi (~> 1.4) 53 | rails-dom-testing (~> 2.0) 54 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 55 | activejob (7.0.4) 56 | activesupport (= 7.0.4) 57 | globalid (>= 0.3.6) 58 | activemodel (7.0.4) 59 | activesupport (= 7.0.4) 60 | activerecord (7.0.4) 61 | activemodel (= 7.0.4) 62 | activesupport (= 7.0.4) 63 | activestorage (7.0.4) 64 | actionpack (= 7.0.4) 65 | activejob (= 7.0.4) 66 | activerecord (= 7.0.4) 67 | activesupport (= 7.0.4) 68 | marcel (~> 1.0) 69 | mini_mime (>= 1.1.0) 70 | activesupport (7.0.4) 71 | concurrent-ruby (~> 1.0, >= 1.0.2) 72 | i18n (>= 1.6, < 2) 73 | minitest (>= 5.1) 74 | tzinfo (~> 2.0) 75 | ast (2.4.2) 76 | builder (3.2.4) 77 | byebug (11.1.3) 78 | coderay (1.1.3) 79 | concurrent-ruby (1.1.10) 80 | crass (1.0.6) 81 | date (3.3.3) 82 | diff-lcs (1.5.1) 83 | erubi (1.12.0) 84 | globalid (1.0.0) 85 | activesupport (>= 5.0) 86 | httparty (0.21.0) 87 | mini_mime (>= 1.0.0) 88 | multi_xml (>= 0.5.2) 89 | i18n (1.12.0) 90 | concurrent-ruby (~> 1.0) 91 | json (2.10.2) 92 | language_server-protocol (3.17.0.4) 93 | lint_roller (1.1.0) 94 | loofah (2.19.1) 95 | crass (~> 1.0.2) 96 | nokogiri (>= 1.5.9) 97 | mail (2.8.0) 98 | mini_mime (>= 0.1.1) 99 | net-imap 100 | net-pop 101 | net-smtp 102 | marcel (1.0.2) 103 | method_source (1.0.0) 104 | mini_mime (1.1.2) 105 | mini_portile2 (2.8.1) 106 | minitest (5.16.3) 107 | multi_xml (0.6.0) 108 | net-imap (0.3.4) 109 | date 110 | net-protocol 111 | net-pop (0.1.2) 112 | net-protocol 113 | net-protocol (0.2.1) 114 | timeout 115 | net-smtp (0.3.3) 116 | net-protocol 117 | nio4r (2.5.8) 118 | nokogiri (1.13.10) 119 | mini_portile2 (~> 2.8.0) 120 | racc (~> 1.4) 121 | parallel (1.26.3) 122 | parser (3.3.7.1) 123 | ast (~> 2.4.1) 124 | racc 125 | pry (0.14.2) 126 | coderay (~> 1.1) 127 | method_source (~> 1.0) 128 | pry-byebug (3.10.1) 129 | byebug (~> 11.0) 130 | pry (>= 0.13, < 0.15) 131 | racc (1.8.1) 132 | rack (2.2.5) 133 | rack-test (2.0.2) 134 | rack (>= 1.3) 135 | rails (7.0.4) 136 | actioncable (= 7.0.4) 137 | actionmailbox (= 7.0.4) 138 | actionmailer (= 7.0.4) 139 | actionpack (= 7.0.4) 140 | actiontext (= 7.0.4) 141 | actionview (= 7.0.4) 142 | activejob (= 7.0.4) 143 | activemodel (= 7.0.4) 144 | activerecord (= 7.0.4) 145 | activestorage (= 7.0.4) 146 | activesupport (= 7.0.4) 147 | bundler (>= 1.15.0) 148 | railties (= 7.0.4) 149 | rails-dom-testing (2.0.3) 150 | activesupport (>= 4.2.0) 151 | nokogiri (>= 1.6) 152 | rails-html-sanitizer (1.4.4) 153 | loofah (~> 2.19, >= 2.19.1) 154 | railties (7.0.4) 155 | actionpack (= 7.0.4) 156 | activesupport (= 7.0.4) 157 | method_source 158 | rake (>= 12.2) 159 | thor (~> 1.0) 160 | zeitwerk (~> 2.5) 161 | rainbow (3.1.1) 162 | rake (13.2.1) 163 | regexp_parser (2.10.0) 164 | rspec (3.13.1) 165 | rspec-core (~> 3.13.0) 166 | rspec-expectations (~> 3.13.0) 167 | rspec-mocks (~> 3.13.0) 168 | rspec-core (3.13.4) 169 | rspec-support (~> 3.13.0) 170 | rspec-expectations (3.13.5) 171 | diff-lcs (>= 1.2.0, < 2.0) 172 | rspec-support (~> 3.13.0) 173 | rspec-mocks (3.13.5) 174 | diff-lcs (>= 1.2.0, < 2.0) 175 | rspec-support (~> 3.13.0) 176 | rspec-support (3.13.4) 177 | rubocop (1.74.0) 178 | json (~> 2.3) 179 | language_server-protocol (~> 3.17.0.2) 180 | lint_roller (~> 1.1.0) 181 | parallel (~> 1.10) 182 | parser (>= 3.3.0.2) 183 | rainbow (>= 2.2.2, < 4.0) 184 | regexp_parser (>= 2.9.3, < 3.0) 185 | rubocop-ast (>= 1.38.0, < 2.0) 186 | ruby-progressbar (~> 1.7) 187 | unicode-display_width (>= 2.4.0, < 4.0) 188 | rubocop-ast (1.38.1) 189 | parser (>= 3.3.1.0) 190 | ruby-progressbar (1.13.0) 191 | thor (1.2.1) 192 | timeout (0.3.1) 193 | tzinfo (2.0.5) 194 | concurrent-ruby (~> 1.0) 195 | unicode-display_width (2.6.0) 196 | websocket-driver (0.7.5) 197 | websocket-extensions (>= 0.1.0) 198 | websocket-extensions (0.1.5) 199 | zeitwerk (2.6.6) 200 | 201 | PLATFORMS 202 | aarch64-linux 203 | arm64-darwin-22 204 | x86_64-darwin-20 205 | x86_64-darwin-21 206 | x86_64-linux 207 | 208 | DEPENDENCIES 209 | httparty (~> 0.21.0) 210 | pry-byebug 211 | rails 212 | rake (~> 13.0) 213 | resend! 214 | rspec (~> 3.0) 215 | rubocop (~> 1.21) 216 | 217 | BUNDLED WITH 218 | 2.3.11 219 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Resend Ruby and Rails SDK 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 4 | ![Build](https://github.com/drish/resend-ruby/actions/workflows/build.yml/badge.svg) 5 | [![Gem Version](https://badge.fury.io/rb/resend.svg)](https://badge.fury.io/rb/resend) 6 | 7 | --- 8 | 9 | ## Installation 10 | 11 | To install Resend Ruby and Rails SDK, simply execute the following command in a terminal: 12 | 13 | Via RubyGems: 14 | 15 | ``` 16 | gem install resend 17 | ``` 18 | 19 | Via Gemfile: 20 | 21 | ``` 22 | gem 'resend' 23 | ``` 24 | 25 | ## Setup 26 | 27 | First, you need to get an API key, which is available in the [Resend Dashboard](https://resend.com). 28 | 29 | ```ruby 30 | require "resend" 31 | Resend.api_key = ENV["RESEND_API_KEY"] 32 | ``` 33 | 34 | or 35 | 36 | ```ruby 37 | require "resend" 38 | Resend.configure do |config| 39 | config.api_key = ENV["RESEND_API_KEY"] 40 | end 41 | ``` 42 | 43 | The `#api_key` method also accepts a block without arguments, which can be useful for lazy or dynamic loading of API keys. 44 | 45 | ```ruby 46 | require "resend" 47 | Resend.api_key = -> { ENV["RESEND_API_KEY"] } 48 | ``` 49 | 50 | ```ruby 51 | Resend.configure do |config| 52 | config.api_key = -> { Current.user.resend_api_key } # Assumes the user has a `resend_api_key` attribute. 53 | end 54 | ``` 55 | 56 | ## Example 57 | 58 | ```rb 59 | require "resend" 60 | 61 | Resend.api_key = ENV["RESEND_API_KEY"] 62 | 63 | params = { 64 | "from": "from@email.io", 65 | "to": ["to@email.com", "to1@gmail.com"], 66 | "html": "

Hello World

", 67 | "subject": "Hey" 68 | } 69 | r = Resend::Emails.send(params) 70 | puts r 71 | ``` 72 | 73 | You can view all the examples in the [examples folder](https://github.com/drish/resend-ruby/tree/main/examples) 74 | 75 | # Rails and ActiveMailer support 76 | 77 | This gem can be used as an ActionMailer delivery method, add this to your `config/environments/environment.rb` file. 78 | 79 | ```ruby 80 | config.action_mailer.delivery_method = :resend 81 | ``` 82 | 83 | Create or update your mailer initializer file and replace the placeholder with your Resend API Key. 84 | 85 | ```rb 86 | # /config/initializers/resend.rb 87 | Resend.api_key = "re_123456" 88 | ``` 89 | 90 | After that you can deliver_now!, example below: 91 | 92 | ```ruby 93 | #/app/mailers/user_mailer 94 | class UserMailer < ApplicationMailer 95 | default from: 'you@yourdomain.io' 96 | def welcome_email 97 | @user = params[:user] 98 | @url = 'http://example.com/login' 99 | mail(to: ["example2@mail.com", "example1@mail.com"], subject: 'Hello from Resend') 100 | end 101 | end 102 | 103 | # anywhere in the app 104 | u = User.new name: "derich" 105 | mailer = UserMailer.with(user: u).welcome_email 106 | mailer.deliver_now! 107 | # => {:id=>"b8f94710-0d84-429c-925a-22d3d8f86916", from: 'you@yourdomain.io', to: ["example2@mail.com", "example1@mail.com"]} 108 | ``` 109 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rspec/core/rake_task" 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | require "rubocop/rake_task" 9 | 10 | RuboCop::RakeTask.new 11 | 12 | task default: %i[spec rubocop] 13 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require "bundler/setup" 5 | require "resend" 6 | 7 | # You can add fixtures and/or initialization code here to make experimenting 8 | # with your gem easier. You can also use a different console, if you like. 9 | 10 | # (If you use this, don't forget to add pry to your Gemfile!) 11 | # require "pry" 12 | # Pry.start 13 | 14 | require "irb" 15 | IRB.start(__FILE__) 16 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | app: 5 | build: . 6 | command: sleep infinity 7 | volumes: 8 | - .:/app 9 | environment: 10 | RESEND_API_KEY: ${RESEND_API_KEY} -------------------------------------------------------------------------------- /examples/api_keys.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../lib/resend" 4 | 5 | raise if ENV["RESEND_API_KEY"].nil? 6 | 7 | Resend.api_key = ENV["RESEND_API_KEY"] 8 | 9 | def create 10 | params = { 11 | name: "name" 12 | } 13 | key = Resend::ApiKeys.create(params) 14 | puts key 15 | end 16 | 17 | def list 18 | keys = Resend::ApiKeys.list 19 | puts keys 20 | end 21 | 22 | def remove 23 | key = Resend::ApiKeys.create({name: "t"}) 24 | puts "created api key id: #{key[:id]}" 25 | Resend::ApiKeys.remove key[:id] 26 | puts "removed #{key[:id]}" 27 | end 28 | 29 | create 30 | # list 31 | # remove 32 | -------------------------------------------------------------------------------- /examples/audiences.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../lib/resend" 4 | 5 | raise if ENV["RESEND_API_KEY"].nil? 6 | 7 | Resend.api_key = ENV["RESEND_API_KEY"] 8 | 9 | def example 10 | params = { 11 | name: "New Audience One", 12 | } 13 | audience = Resend::Audiences.create(params) 14 | puts "Created new audience: #{audience[:id]}" 15 | 16 | Resend::Audiences.get(audience[:id]) 17 | puts "retrieved audience id: #{audience[:id]}" 18 | 19 | audiences = Resend::Audiences.list 20 | puts audiences 21 | 22 | Resend::Audiences.remove audience[:id] 23 | puts "deleted #{audience[:id]}" 24 | end 25 | 26 | example -------------------------------------------------------------------------------- /examples/batch_email.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../lib/resend" 4 | 5 | raise if ENV["RESEND_API_KEY"].nil? 6 | 7 | Resend.api_key = ENV["RESEND_API_KEY"] 8 | 9 | params = [ 10 | { 11 | "from": "onboarding@resend.dev", 12 | "to": ["delivered@resend.dev"], 13 | "subject": "hey", 14 | "html": "hello, world!", 15 | }, 16 | { 17 | "from": "onboarding@resend.dev", 18 | "to": ["delivered@resend.dev"], 19 | "subject": "hello", 20 | "html": "hello, world!", 21 | }, 22 | ] 23 | 24 | # Send a batch of emails without Idempotency key 25 | emails = Resend::Batch.send(params) 26 | puts "Emails sent without Idempotency key:" 27 | puts(emails) 28 | 29 | # Send a batch of emails with Idempotency key 30 | emails = Resend::Batch.send(params, options: { idempotency_key: "af67ff1cdf3cdf1" }) 31 | puts "Emails sent with Idempotency key:" 32 | puts(emails) -------------------------------------------------------------------------------- /examples/broadcasts.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../lib/resend" 4 | 5 | raise if ENV["RESEND_API_KEY"].nil? 6 | 7 | Resend.api_key = ENV["RESEND_API_KEY"] 8 | 9 | # replace with an existing audience id 10 | audience_id = "78b8d3bc-a55a-45a3-aee6-6ec0a5e13d7e" 11 | 12 | create_params = { 13 | from: "onboarding@resend.dev", 14 | subject: "Hello from Ruby SDK", 15 | audience_id: audience_id, 16 | text: "Hello, how are you?", 17 | name: "Hello from Ruby SDK", 18 | } 19 | 20 | broadcast = Resend::Broadcasts.create(create_params) 21 | puts "created broadcast: #{broadcast[:id]}" 22 | 23 | update_params = { 24 | broadcast_id: broadcast[:id], 25 | name: "Hello from Ruby SDK - updated", 26 | } 27 | 28 | updated_broadcast = Resend::Broadcasts.update(update_params) 29 | puts "updated broadcast: #{updated_broadcast[:id]}" 30 | 31 | send_params = { 32 | broadcast_id: broadcast[:id], 33 | scheduled_at: "in 1 min", 34 | } 35 | 36 | sent_broadcast = Resend::Broadcasts.send(send_params) 37 | puts "sent broadcast: #{sent_broadcast[:id]}" 38 | 39 | broadcasts = Resend::Broadcasts.list 40 | puts broadcasts 41 | 42 | retrieved = Resend::Broadcasts.get(broadcast[:id]) 43 | puts "retrieved #{retrieved[:id]}" 44 | 45 | if retrieved[:status] == 'draft' 46 | Resend::Broadcasts.remove(broadcast[:id]) 47 | puts "removed #{broadcast[:id]}" 48 | else 49 | puts 'Cannot remove a broadcast that is not in draft status' 50 | end -------------------------------------------------------------------------------- /examples/contacts.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../lib/resend" 4 | 5 | raise if ENV["RESEND_API_KEY"].nil? 6 | 7 | Resend.api_key = ENV["RESEND_API_KEY"] 8 | 9 | def example 10 | 11 | audience_id = "ca4e37c5-a82a-4199-a3b8-bf912a6472aa" 12 | 13 | params = { 14 | audience_id: audience_id, 15 | email: "steve@example.com", 16 | first_name: "Steve", 17 | last_name: "Woz", 18 | unsubscribed: false, 19 | } 20 | 21 | contact = Resend::Contacts.create(params) 22 | puts "Contact created: #{contact}" 23 | 24 | update_params = { 25 | audience_id: audience_id, 26 | email: params[:email], 27 | # id: contact[:id], 28 | unsubscribed: false, 29 | first_name: "Updated", 30 | } 31 | 32 | retrieved = Resend::Contacts.get(audience_id, contact[:id]) 33 | puts "Retrived contact by ID" 34 | puts retrieved 35 | 36 | retrieved_by_email = Resend::Contacts.get(audience_id, contact[:email]) 37 | puts "Retrived contact by Email" 38 | puts retrieved_by_email 39 | 40 | updated = Resend::Contacts.update(update_params) 41 | puts "Updated contact: #{updated}" 42 | 43 | contacts = Resend::Contacts.list(audience_id) 44 | puts contacts 45 | 46 | # delete by id 47 | del = Resend::Contacts.remove(audience_id, contact[:id]) 48 | 49 | # delete by email 50 | # del = Resend::Contacts.remove(audience_id, "steve@example.com") 51 | 52 | puts "Deleted #{del}" 53 | end 54 | 55 | example -------------------------------------------------------------------------------- /examples/domains.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../lib/resend" 4 | 5 | raise if ENV["RESEND_API_KEY"].nil? 6 | 7 | Resend.api_key = ENV["RESEND_API_KEY"] 8 | 9 | def create 10 | params = { 11 | name: "name", 12 | region: "" 13 | } 14 | domain = Resend::Domains.create(params) 15 | puts domain 16 | 17 | Resend::Domains.remove domain[:id] 18 | puts "deleted #{domain[:id]}" 19 | end 20 | 21 | def get 22 | domain = Resend::Domains.create({name: "test"}) 23 | puts "created domain id: #{domain[:id]}" 24 | 25 | retrieved = Resend::Domains.get domain[:id] 26 | puts "retrieved domain: #{retrieved[:id]}" 27 | puts retrieved 28 | 29 | Resend::Domains.remove retrieved[:id] 30 | puts "deleted #{retrieved[:id]}" 31 | end 32 | 33 | def update 34 | domain = Resend::Domains.create({name: "test"}) 35 | puts "created domain id: #{domain[:id]}" 36 | 37 | params = { 38 | id: domain[:id], 39 | open_tracking: false, 40 | click_tracking: false, 41 | tls: "enforced" 42 | } 43 | updated = Resend::Domains.update(params) 44 | puts "updated domain: #{updated[:id]}" 45 | puts updated 46 | end 47 | 48 | def list 49 | domains = Resend::Domains.list 50 | puts domains 51 | end 52 | 53 | def remove 54 | domain = Resend::Domains.create({name: "test"}) 55 | puts "created domain id: #{domain[:id]}" 56 | Resend::Domains.remove domain[:id] 57 | puts "deleted #{domain[:id]}" 58 | end 59 | 60 | def verify 61 | domain = Resend::Domains.create({name: "test2"}) 62 | puts "created domain id: #{domain[:id]}" 63 | 64 | Resend::Domains.verify domain[:id] 65 | puts "verified #{domain[:id]}" 66 | 67 | Resend::Domains.remove domain[:id] 68 | puts "deleted #{domain[:id]}" 69 | end 70 | 71 | create 72 | get 73 | update 74 | list 75 | remove 76 | verify 77 | -------------------------------------------------------------------------------- /examples/schedule_email.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../lib/resend" 4 | 5 | raise if ENV["RESEND_API_KEY"].nil? 6 | 7 | Resend.api_key = ENV["RESEND_API_KEY"] 8 | 9 | params = { 10 | "from": "onboarding@resend.dev", 11 | "to": ["delivered@resend.dev"], 12 | "subject": "Hello from Resend (Ruby SDK) - Scheduled", 13 | "html": "Hello, from Resend (Ruby SDK)", 14 | 15 | # ISO 8601 format, you can get that in Ruby with 16 | # DateTime.now.iso8601(3) 17 | "scheduled_at": "2024-09-05T11:52:01.858Z", 18 | } 19 | 20 | email = Resend::Emails.send(params) 21 | puts(email) 22 | 23 | update_params = { 24 | "email_id": email[:id], 25 | "scheduled_at": "2024-11-05T11:52:01.858Z", 26 | } 27 | 28 | updated_email = Resend::Emails.update(update_params) 29 | puts(updated_email) 30 | 31 | canceled = Resend::Emails.cancel(updated_email[:id]) 32 | puts(canceled) -------------------------------------------------------------------------------- /examples/simple_mail.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../lib/resend" 4 | 5 | raise if ENV["RESEND_API_KEY"].nil? 6 | 7 | Resend.api_key = ENV["RESEND_API_KEY"] 8 | 9 | params = { 10 | "from": "onboarding@resend.dev", 11 | "to": ["delivered@resend.dev"], 12 | "subject": "Hello from Resend Ruby SDK", 13 | "text": "test", 14 | "tags": { 15 | "country": "br" 16 | } 17 | } 18 | 19 | # With Idempotency 20 | puts "Sending email with Idempotency key:" 21 | email = Resend::Emails.send(params, options: { idempotency_key: "123" }) 22 | puts(email) 23 | 24 | # Without Idempotency 25 | puts "Sending email without Idempotency key:" 26 | email = Resend::Emails.send(params) 27 | puts(email) 28 | 29 | email = Resend::Emails.get(email[:id]) 30 | puts(email[:id]) -------------------------------------------------------------------------------- /examples/with_attachments.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../lib/resend" 4 | require "stringio" 5 | 6 | raise if ENV["RESEND_API_KEY"].nil? 7 | 8 | Resend.api_key = ENV["RESEND_API_KEY"] 9 | 10 | file = IO.read(File.join(File.dirname(__FILE__), "../resources/invoice.pdf")) 11 | 12 | params = { 13 | "from": "from@email.io", 14 | "to": ["to@hi.com"], 15 | "text": "heyo", 16 | "subject": "Hello with attachment", 17 | "attachments": [ 18 | { 19 | "filename": "invoice.pdf", # local file 20 | "content": file.bytes # make sure to use .bytes() here 21 | }, 22 | { 23 | "path": "https://github.com/resendlabs/resend-go/raw/main/resources/invoice.pdf", 24 | "filename": "from_external_path.pdf" 25 | } 26 | ] 27 | } 28 | 29 | sent = Resend::Emails.send(params) 30 | puts sent 31 | -------------------------------------------------------------------------------- /examples/with_b64_attachments.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "../lib/resend" 4 | require "stringio" 5 | require "base64" 6 | 7 | raise if ENV["RESEND_API_KEY"].nil? 8 | 9 | Resend.api_key = ENV["RESEND_API_KEY"] 10 | 11 | file = IO.read(File.join(File.dirname(__FILE__), "../resources/invoice.pdf")) 12 | 13 | params = { 14 | "from": "onboarding@resend.dev", 15 | "to": ["delivered@resend.dev"], 16 | "text": "heyo", 17 | "subject": "Hello with base 64 encoded attachment", 18 | "attachments": [ 19 | { 20 | "filename": "invoice.pdf", 21 | "content": Base64.encode64(file).to_s 22 | }, 23 | ] 24 | } 25 | 26 | sent = Resend::Emails.send(params) 27 | puts sent 28 | -------------------------------------------------------------------------------- /lib/resend.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Version 4 | require "resend/version" 5 | 6 | # Utils 7 | require "httparty" 8 | require "json" 9 | require "resend/errors" 10 | require "resend/client" 11 | require "resend/request" 12 | 13 | # API Operations 14 | require "resend/audiences" 15 | require "resend/api_keys" 16 | require "resend/broadcasts" 17 | require "resend/batch" 18 | require "resend/contacts" 19 | require "resend/domains" 20 | require "resend/emails" 21 | 22 | # Rails 23 | require "resend/railtie" if defined?(Rails) && defined?(ActionMailer) 24 | 25 | # Main Resend module 26 | module Resend 27 | class << self 28 | attr_accessor :api_key 29 | 30 | def configure 31 | yield self if block_given? 32 | true 33 | end 34 | alias config configure 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/resend/api_keys.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Resend 4 | # api keys api wrapper 5 | module ApiKeys 6 | class << self 7 | # https://resend.com/docs/api-reference/api-keys/create-api-key 8 | def create(params) 9 | path = "api-keys" 10 | Resend::Request.new(path, params, "post").perform 11 | end 12 | 13 | # https://resend.com/docs/api-reference/api-keys/list-api-keys 14 | def list 15 | path = "api-keys" 16 | Resend::Request.new(path, {}, "get").perform 17 | end 18 | 19 | # https://resend.com/docs/api-reference/api-keys/delete-api-key 20 | def remove(api_key_id = "") 21 | path = "api-keys/#{api_key_id}" 22 | Resend::Request.new(path, {}, "delete").perform 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/resend/audiences.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Resend 4 | # Audiences api wrapper 5 | module Audiences 6 | class << self 7 | # https://resend.com/docs/api-reference/audiences/create-audience 8 | def create(params) 9 | path = "audiences" 10 | Resend::Request.new(path, params, "post").perform 11 | end 12 | 13 | # https://resend.com/docs/api-reference/audiences/get-audience 14 | def get(audience_id = "") 15 | path = "audiences/#{audience_id}" 16 | Resend::Request.new(path, {}, "get").perform 17 | end 18 | 19 | # https://resend.com/docs/api-reference/audiences/list-audiences 20 | def list 21 | path = "audiences" 22 | Resend::Request.new(path, {}, "get").perform 23 | end 24 | 25 | # https://resend.com/docs/api-reference/audiences/delete-audience 26 | def remove(audience_id = "") 27 | path = "audiences/#{audience_id}" 28 | Resend::Request.new(path, {}, "delete").perform 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/resend/batch.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Resend 4 | # Module responsible for wrapping Batch email sending API 5 | module Batch 6 | class << self 7 | # https://resend.com/docs/api-reference/emails/send-batch-emails 8 | def send(params = [], options: {}) 9 | path = "emails/batch" 10 | 11 | Resend::Request.new(path, params, "post", options: options).perform 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/resend/broadcasts.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Resend 4 | # broadcasts api wrapper 5 | module Broadcasts 6 | class << self 7 | # https://resend.com/docs/api-reference/broadcasts/create-broadcast 8 | def create(params = {}) 9 | path = "broadcasts" 10 | Resend::Request.new(path, params, "post").perform 11 | end 12 | 13 | # https://resend.com/docs/api-reference/broadcasts/update-broadcast 14 | def update(params = {}) 15 | path = "broadcasts/#{params[:broadcast_id]}" 16 | Resend::Request.new(path, params, "patch").perform 17 | end 18 | 19 | # https://resend.com/docs/api-reference/broadcasts/send-broadcast 20 | def send(params = {}) 21 | path = "broadcasts/#{params[:broadcast_id]}/send" 22 | Resend::Request.new(path, params, "post").perform 23 | end 24 | 25 | # https://resend.com/docs/api-reference/broadcasts/list-broadcasts 26 | def list 27 | path = "broadcasts" 28 | Resend::Request.new(path, {}, "get").perform 29 | end 30 | 31 | # https://resend.com/docs/api-reference/broadcasts/delete-broadcast 32 | def remove(broadcast_id = "") 33 | path = "broadcasts/#{broadcast_id}" 34 | Resend::Request.new(path, {}, "delete").perform 35 | end 36 | 37 | # https://resend.com/docs/api-reference/broadcasts/get-broadcast 38 | def get(broadcast_id = "") 39 | path = "broadcasts/#{broadcast_id}" 40 | Resend::Request.new(path, {}, "get").perform 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/resend/client.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # backwards compatibility 4 | require "resend/emails" 5 | 6 | module Resend 7 | # Client class. 8 | class Client 9 | include Resend::Emails 10 | 11 | attr_reader :api_key 12 | 13 | def initialize(api_key) 14 | raise ArgumentError, "API Key is not a string" unless api_key.is_a?(String) 15 | 16 | @api_key = api_key 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/resend/contacts.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Resend 4 | # Contacts api wrapper 5 | module Contacts 6 | class << self 7 | # https://resend.com/docs/api-reference/contacts/create-contact 8 | def create(params) 9 | path = "audiences/#{params[:audience_id]}/contacts" 10 | Resend::Request.new(path, params, "post").perform 11 | end 12 | 13 | # 14 | # Retrieves a contact from an audience 15 | # 16 | # @param audience_id [String] the audience id 17 | # @param id [String] either the contact id or contact's email 18 | # 19 | # https://resend.com/docs/api-reference/contacts/get-contact 20 | def get(audience_id, id) 21 | path = "audiences/#{audience_id}/contacts/#{id}" 22 | Resend::Request.new(path, {}, "get").perform 23 | end 24 | 25 | # 26 | # List contacts in an audience 27 | # 28 | # @param audience_id [String] the audience id 29 | # https://resend.com/docs/api-reference/contacts/list-contacts 30 | def list(audience_id) 31 | path = "audiences/#{audience_id}/contacts" 32 | Resend::Request.new(path, {}, "get").perform 33 | end 34 | 35 | # 36 | # Remove a contact from an audience 37 | # 38 | # @param audience_id [String] the audience id 39 | # @param contact_id [String] either the contact id or contact email 40 | # 41 | # see also: https://resend.com/docs/api-reference/contacts/delete-contact 42 | def remove(audience_id, contact_id) 43 | path = "audiences/#{audience_id}/contacts/#{contact_id}" 44 | Resend::Request.new(path, {}, "delete").perform 45 | end 46 | 47 | # 48 | # Update a contact 49 | # 50 | # @param params [Hash] the contact params 51 | # https://resend.com/docs/api-reference/contacts/update-contact 52 | def update(params) 53 | raise ArgumentError, "id or email is required" if params[:id].nil? && params[:email].nil? 54 | 55 | path = "audiences/#{params[:audience_id]}/contacts/#{params[:id] || params[:email]}" 56 | Resend::Request.new(path, params, "patch").perform 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/resend/domains.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Resend 4 | # domains api wrapper 5 | module Domains 6 | class << self 7 | # https://resend.com/docs/api-reference/domains/create-domain 8 | def create(params) 9 | path = "domains" 10 | Resend::Request.new(path, params, "post").perform 11 | end 12 | 13 | # https://resend.com/docs/api-reference/domains/update-domain 14 | def update(params) 15 | path = "domains/#{params[:id]}" 16 | Resend::Request.new(path, params, "patch").perform 17 | end 18 | 19 | # https://resend.com/docs/api-reference/domains/get-domain 20 | def get(domain_id = "") 21 | path = "domains/#{domain_id}" 22 | Resend::Request.new(path, {}, "get").perform 23 | end 24 | 25 | # https://resend.com/docs/api-reference/api-keys/list-api-keys 26 | def list 27 | path = "domains" 28 | Resend::Request.new(path, {}, "get").perform 29 | end 30 | 31 | # https://resend.com/docs/api-reference/domains/delete-domain 32 | def remove(domain_id = "") 33 | path = "domains/#{domain_id}" 34 | Resend::Request.new(path, {}, "delete").perform 35 | end 36 | 37 | # https://resend.com/docs/api-reference/domains/verify-domain 38 | def verify(domain_id = "") 39 | path = "domains/#{domain_id}/verify" 40 | Resend::Request.new(path, {}, "post").perform 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/resend/emails.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Resend 4 | # Module responsible for wrapping email sending API 5 | module Emails 6 | class << self 7 | # Sends or schedules an email. 8 | # see more: https://resend.com/docs/api-reference/emails/send-email 9 | def send(params, options: {}) 10 | path = "emails" 11 | Resend::Request.new(path, params, "post", options: options).perform 12 | end 13 | 14 | # Retrieve a single email. 15 | # see more: https://resend.com/docs/api-reference/emails/retrieve-email 16 | def get(email_id = "") 17 | path = "emails/#{email_id}" 18 | Resend::Request.new(path, {}, "get").perform 19 | end 20 | 21 | # Update a scheduled email. 22 | # see more: https://resend.com/docs/api-reference/emails/update-email 23 | def update(params) 24 | path = "emails/#{params[:email_id]}" 25 | Resend::Request.new(path, params, "patch").perform 26 | end 27 | 28 | # Cancel a scheduled email. 29 | # see more: https://resend.com/docs/api-reference/emails/cancel-email 30 | def cancel(email_id = "") 31 | path = "emails/#{email_id}/cancel" 32 | Resend::Request.new(path, {}, "post").perform 33 | end 34 | end 35 | 36 | # This method is kept here for backwards compatibility 37 | # Use Resend::Emails.send instead. 38 | def send_email(params) 39 | warn "[DEPRECATION] `send_email` is deprecated. Please use `Resend::Emails.send` instead." 40 | Resend::Emails.send(params) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/resend/errors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Resend 4 | # Errors wrapper class 5 | # For more info: https://resend.com/docs/api-reference/error-codes 6 | class Error < StandardError 7 | # 4xx HTTP status code 8 | ClientError = Class.new(self) 9 | 10 | # 5xx HTTP status code 11 | ServerError = Class.new(self) 12 | 13 | # code 500 14 | InternalServerError = Class.new(ServerError) 15 | 16 | # code 422 17 | InvalidRequestError = Class.new(ServerError) 18 | 19 | # code 429 20 | RateLimitExceededError = Class.new(ServerError) 21 | 22 | # code 404 23 | NotFoundError = Class.new(ServerError) 24 | 25 | ERRORS = { 26 | 401 => Resend::Error::InvalidRequestError, 27 | 404 => Resend::Error::InvalidRequestError, 28 | 422 => Resend::Error::InvalidRequestError, 29 | 429 => Resend::Error::RateLimitExceededError, 30 | 400 => Resend::Error::InvalidRequestError, 31 | 500 => Resend::Error::InternalServerError 32 | }.freeze 33 | 34 | def initialize(msg, code = nil) 35 | super(msg) 36 | @code = code 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/resend/mailer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "resend" 4 | 5 | module Resend 6 | # Mailer class used by railtie 7 | class Mailer 8 | attr_accessor :config, :settings 9 | 10 | # These are set as `headers` by the Rails API, but these will be filtered out 11 | # when constructing the Resend API payload, since they're are sent as post params. 12 | # https://resend.com/docs/api-reference/emails/send-email 13 | IGNORED_HEADERS = %w[ 14 | cc bcc 15 | from reply-to to subject mime-version 16 | html text 17 | content-type tags scheduled_at 18 | headers options 19 | ].freeze 20 | 21 | SUPPORTED_OPTIONS = %w[idempotency_key].freeze 22 | 23 | def initialize(config) 24 | @config = config 25 | raise Resend::Error.new("Make sure your API Key is set", @config) unless Resend.api_key 26 | 27 | # avoids NilError exception 28 | @settings = { return_response: true } 29 | end 30 | 31 | # 32 | # Overwritten deliver! method 33 | # 34 | # @param Mail mail 35 | # 36 | # @return Object resend response 37 | # 38 | def deliver!(mail) 39 | params = build_resend_params(mail) 40 | options = get_options(mail) if mail[:options].present? 41 | resp = Resend::Emails.send(params, options: options || {}) 42 | mail.message_id = resp[:id] if resp[:error].nil? 43 | resp 44 | end 45 | 46 | # 47 | # Builds the payload for sending 48 | # 49 | # @param Mail mail rails mail object 50 | # 51 | # @return Hash hash with all Resend params 52 | # 53 | def build_resend_params(mail) 54 | params = { 55 | from: get_from(mail), 56 | to: mail.to, 57 | subject: mail.subject 58 | } 59 | params.merge!(get_addons(mail)) 60 | params.merge!(get_headers(mail)) 61 | params.merge!(get_tags(mail)) 62 | params[:attachments] = get_attachments(mail) if mail.attachments.present? 63 | params.merge!(get_contents(mail)) 64 | params 65 | end 66 | 67 | # 68 | # Add custom headers fields. 69 | # 70 | # Both ways are supported: 71 | # 72 | # 1. Through the `#mail()` method ie: 73 | # mail(headers: { "X-Custom-Header" => "value" }) 74 | # 75 | # 2. Through the Rails `#headers` method ie: 76 | # headers["X-Custom-Header"] = "value" 77 | # 78 | # 79 | # setting the header values through the `#mail` method will overwrite values set 80 | # through the `#headers` method using the same key. 81 | # 82 | # @param Mail mail Rails Mail object 83 | # 84 | # @return Hash hash with headers param 85 | # 86 | def get_headers(mail) 87 | params = {} 88 | 89 | if mail[:headers].present? || unignored_headers(mail).present? 90 | params[:headers] = {} 91 | params[:headers].merge!(headers_values(mail)) if unignored_headers(mail).present? 92 | params[:headers].merge!(mail_headers_values(mail)) if mail[:headers].present? 93 | end 94 | 95 | params 96 | end 97 | 98 | # 99 | # Adds additional options fields. 100 | # Currently supports only :idempotency_key 101 | # 102 | # @param Mail mail Rails Mail object 103 | # @return Hash hash with headers param 104 | # 105 | def get_options(mail) 106 | opts = {} 107 | if mail[:options].present? 108 | opts.merge!(mail[:options].unparsed_value) 109 | opts.delete_if { |k, _v| !SUPPORTED_OPTIONS.include?(k.to_s) } 110 | end 111 | opts 112 | end 113 | 114 | # Remove nils from header values 115 | def cleanup_headers(headers) 116 | headers.delete_if { |_k, v| v.nil? } 117 | end 118 | 119 | # Gets the values of the headers that are set through the `#mail` method 120 | # 121 | # @param Mail mail Rails Mail object 122 | # @return Hash hash with mail headers values 123 | def mail_headers_values(mail) 124 | params = {} 125 | mail[:headers].unparsed_value.each do |k, v| 126 | params[k.to_s] = v 127 | end 128 | cleanup_headers(params) 129 | params 130 | end 131 | 132 | # Gets the values of the headers that are set through the `#headers` method 133 | # 134 | # @param Mail mail Rails Mail object 135 | # @return Hash hash with headers values 136 | def headers_values(mail) 137 | params = {} 138 | unignored_headers(mail).each do |h| 139 | params[h.name.to_s] = h.unparsed_value 140 | end 141 | cleanup_headers(params) 142 | params 143 | end 144 | 145 | # 146 | # Add tags fields 147 | # 148 | # @param Mail mail Rails Mail object 149 | # 150 | # @return Hash hash with tags param 151 | # 152 | def get_tags(mail) 153 | params = {} 154 | params[:tags] = mail[:tags].unparsed_value if mail[:tags].present? 155 | params 156 | end 157 | 158 | # 159 | # Add cc, bcc, reply_to fields 160 | # 161 | # @param Mail mail Rails Mail Object 162 | # 163 | # @return Hash hash containing cc/bcc/reply_to attrs 164 | # 165 | def get_addons(mail) 166 | params = {} 167 | params[:cc] = mail.cc if mail.cc.present? 168 | params[:bcc] = mail.bcc if mail.bcc.present? 169 | params[:reply_to] = mail.reply_to if mail.reply_to.present? 170 | params 171 | end 172 | 173 | # 174 | # Gets the body of the email 175 | # 176 | # @param Mail mail Rails Mail Object 177 | # 178 | # @return Hash hash containing html/text or both attrs 179 | # 180 | def get_contents(mail) 181 | params = {} 182 | case mail.mime_type 183 | when "text/plain" 184 | params[:text] = mail.body.decoded 185 | when "text/html" 186 | params[:html] = mail.body.decoded 187 | when "multipart/alternative", "multipart/mixed", "multipart/related" 188 | params[:text] = mail.text_part.decoded if mail.text_part 189 | params[:html] = mail.html_part.decoded if mail.html_part 190 | end 191 | params 192 | end 193 | 194 | # 195 | # Properly gets the `from` attr 196 | # 197 | # @param Mail input object 198 | # 199 | # @return String `from` string 200 | # 201 | def get_from(input) 202 | return input.from.first if input[:from].nil? 203 | 204 | from = input[:from].formatted 205 | return from.first if from.is_a? Array 206 | 207 | from.to_s 208 | end 209 | 210 | # 211 | # Handle attachments when present 212 | # 213 | # @return Array attachments array 214 | # 215 | def get_attachments(mail) 216 | attachments = [] 217 | mail.attachments.each do |part| 218 | attachment = { 219 | filename: part.filename, 220 | content: part.body.decoded.bytes 221 | } 222 | attachments.append(attachment) 223 | end 224 | attachments 225 | end 226 | 227 | # 228 | # Get all headers that are not ignored 229 | # 230 | # @param Mail mail 231 | # 232 | # @return Array headers 233 | # 234 | def unignored_headers(mail) 235 | @unignored_headers ||= mail.header_fields.reject { |h| IGNORED_HEADERS.include?(h.name.downcase) } 236 | end 237 | end 238 | end 239 | -------------------------------------------------------------------------------- /lib/resend/railtie.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "resend" 4 | require "resend/mailer" 5 | 6 | module Resend 7 | # Main railtime class 8 | class Railtie < ::Rails::Railtie 9 | ActiveSupport.on_load(:action_mailer) do 10 | add_delivery_method :resend, Resend::Mailer 11 | ActiveSupport.run_load_hooks(:resend_mailer, Resend::Mailer) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/resend/request.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Resend 4 | # This class is responsible for making the appropriate HTTP calls 5 | # and raising the specific errors based on the response. 6 | class Request 7 | BASE_URL = ENV["RESEND_BASE_URL"] || "https://api.resend.com/" 8 | 9 | attr_accessor :body, :verb, :options 10 | 11 | def initialize(path = "", body = {}, verb = "POST", options: {}) 12 | raise if (api_key = Resend.api_key).nil? 13 | 14 | api_key = api_key.call if api_key.is_a?(Proc) 15 | 16 | @path = path 17 | @body = body 18 | @verb = verb 19 | @options = options 20 | @headers = { 21 | "Content-Type" => "application/json", 22 | "Accept" => "application/json", 23 | "User-Agent" => "resend-ruby:#{Resend::VERSION}", 24 | "Authorization" => "Bearer #{api_key}" 25 | } 26 | 27 | set_idempotency_key 28 | end 29 | 30 | # Performs the HTTP call 31 | def perform 32 | options = { 33 | headers: @headers 34 | } 35 | options[:body] = @body.to_json unless @body.empty? 36 | 37 | resp = HTTParty.send(@verb.to_sym, "#{BASE_URL}#{@path}", options) 38 | 39 | check_json!(resp) 40 | 41 | resp.transform_keys!(&:to_sym) unless resp.body.empty? 42 | handle_error!(resp) if resp[:statusCode] && (resp[:statusCode] != 200 || resp[:statusCode] != 201) 43 | resp 44 | end 45 | 46 | def handle_error!(resp) 47 | code = resp[:statusCode] 48 | body = resp[:message] 49 | 50 | # get error from the known list of errors 51 | error = Resend::Error::ERRORS[code] 52 | raise(error.new(body, code)) if error 53 | 54 | # Raise generic Resend error when the error code is not part of the known errors 55 | raise Resend::Error.new(body, code) 56 | end 57 | 58 | private 59 | 60 | def set_idempotency_key 61 | # Only set idempotency key if the verb is POST for now. 62 | # 63 | # Does not set it if the idempotency_key is nil or empty 64 | if @verb.downcase == "post" && (!@options[:idempotency_key].nil? && !@options[:idempotency_key].empty?) 65 | @headers["Idempotency-Key"] = @options[:idempotency_key] 66 | end 67 | end 68 | 69 | def check_json!(resp) 70 | if resp.body.is_a?(Hash) 71 | JSON.parse(resp.body.to_json) 72 | else 73 | JSON.parse(resp.body) 74 | end 75 | rescue JSON::ParserError, TypeError 76 | raise Resend::Error::InternalServerError.new("Resend API returned an unexpected response", nil) 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/resend/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Resend 4 | VERSION = "0.22.0" 5 | end 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /resend.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/resend/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "resend" 7 | spec.version = Resend::VERSION 8 | spec.summary = "The Ruby and Rails SDK for resend.com" 9 | spec.homepage = "https://github.com/resend/resend-ruby" 10 | spec.license = "MIT" 11 | 12 | spec.author = "Derich Pacheco" 13 | spec.email = "carlosderich@gmail.com" 14 | 15 | spec.files = Dir["*.{md,txt}", "{lib}/**/*"] 16 | spec.require_path = "lib" 17 | spec.required_ruby_version = ">= 2.6" 18 | spec.add_dependency "httparty", ">= 0.21.0" 19 | spec.add_development_dependency "rails" 20 | end 21 | -------------------------------------------------------------------------------- /resources/invoice.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << /Length 2 0 R >> 5 | stream 6 | 0.574574 0 0.040483 -0.011009 0.511009 0.604048 d1 7 | 8 | endstream 9 | endobj 10 | 11 | 2 0 obj 12 | 51 13 | endobj 14 | 15 | 3 0 obj 16 | << /Length 4 0 R >> 17 | stream 18 | 0.556818 0 0.034091 -0.204545 0.522727 0.784091 d1 19 | 20 | endstream 21 | endobj 22 | 23 | 4 0 obj 24 | 51 25 | endobj 26 | 27 | 5 0 obj 28 | << /Length 6 0 R >> 29 | stream 30 | 0.248224 0 0.000000 0.000000 0.248224 1.000000 d1 31 | 32 | endstream 33 | endobj 34 | 35 | 6 0 obj 36 | 50 37 | endobj 38 | 39 | 7 0 obj 40 | << /Length 8 0 R >> 41 | stream 42 | 0.575994 0 0.022727 -0.204545 0.553267 0.772727 d1 43 | 44 | endstream 45 | endobj 46 | 47 | 8 0 obj 48 | 51 49 | endobj 50 | 51 | 9 0 obj 52 | << /Length 10 0 R >> 53 | stream 54 | 0.643466 0 0.071733 0.000000 0.600852 0.799006 d1 55 | 56 | endstream 57 | endobj 58 | 59 | 10 0 obj 60 | 50 61 | endobj 62 | 63 | 11 0 obj 64 | << /Length 12 0 R >> 65 | stream 66 | 0.596591 0 0.051136 -0.011364 0.545455 0.615057 d1 67 | 68 | endstream 69 | endobj 70 | 71 | 12 0 obj 72 | 51 73 | endobj 74 | 75 | 13 0 obj 76 | << /Length 14 0 R >> 77 | stream 78 | 0.609730 0 0.066051 0.000000 0.545099 0.618608 d1 79 | 80 | endstream 81 | endobj 82 | 83 | 14 0 obj 84 | 50 85 | endobj 86 | 87 | 15 0 obj 88 | << /Length 16 0 R >> 89 | stream 90 | 0.281250 0 0.000000 0.000000 0.281250 1.000000 d1 91 | 92 | endstream 93 | endobj 94 | 95 | 16 0 obj 96 | 50 97 | endobj 98 | 99 | 17 0 obj 100 | << /Length 18 0 R >> 101 | stream 102 | 0.650568 0 0.071733 0.000000 0.627486 0.799006 d1 103 | 104 | endstream 105 | endobj 106 | 107 | 18 0 obj 108 | 50 109 | endobj 110 | 111 | 19 0 obj 112 | << /Length 20 0 R >> 113 | stream 114 | 0.563920 0 0.051136 -0.012784 0.487216 0.616477 d1 115 | 116 | endstream 117 | endobj 118 | 119 | 20 0 obj 120 | 51 121 | endobj 122 | 123 | 21 0 obj 124 | << /Length 22 0 R >> 125 | stream 126 | 0.607955 0 0.042614 -0.010653 0.565341 0.605824 d1 127 | 128 | endstream 129 | endobj 130 | 131 | 22 0 obj 132 | 51 133 | endobj 134 | 135 | 23 0 obj 136 | << /Length 24 0 R >> 137 | stream 138 | 0.590909 0 0.076705 0.000000 0.514205 0.803977 d1 139 | 140 | endstream 141 | endobj 142 | 143 | 24 0 obj 144 | 50 145 | endobj 146 | 147 | 25 0 obj 148 | << /Length 26 0 R >> 149 | stream 150 | 0.608310 0 0.066051 -0.007102 0.542259 0.618608 d1 151 | 152 | endstream 153 | endobj 154 | 155 | 26 0 obj 156 | 51 157 | endobj 158 | 159 | 27 0 obj 160 | << /Length 28 0 R >> 161 | stream 162 | 0.869318 0 0.076705 0.000000 0.792614 0.629261 d1 163 | 164 | endstream 165 | endobj 166 | 167 | 28 0 obj 168 | 50 169 | endobj 170 | 171 | 29 0 obj 172 | << /Length 30 0 R >> 173 | stream 174 | 0.577415 0 0.042614 -0.010653 0.537642 0.605824 d1 175 | 176 | endstream 177 | endobj 178 | 179 | 30 0 obj 180 | 51 181 | endobj 182 | 183 | 31 0 obj 184 | << /Length 32 0 R >> 185 | stream 186 | 0.642045 0 0.048295 0.000000 0.593750 0.775568 d1 187 | 188 | endstream 189 | endobj 190 | 191 | 32 0 obj 192 | 50 193 | endobj 194 | 195 | 33 0 obj 196 | << /Length 34 0 R >> 197 | stream 198 | 0.379972 0 0.024858 -0.008878 0.348366 0.709872 d1 199 | 200 | endstream 201 | endobj 202 | 203 | 34 0 obj 204 | 51 205 | endobj 206 | 207 | 35 0 obj 208 | << /Length 36 0 R >> 209 | stream 210 | 0.897727 0 0.066051 0.000000 0.833097 0.618608 d1 211 | 212 | endstream 213 | endobj 214 | 215 | 36 0 obj 216 | 50 217 | endobj 218 | 219 | 37 0 obj 220 | << /Length 38 0 R >> 221 | stream 222 | 0.396662 0 0.066051 0.000000 0.377131 0.619318 d1 223 | 224 | endstream 225 | endobj 226 | 227 | 38 0 obj 228 | 50 229 | endobj 230 | 231 | 39 0 obj 232 | << /Length 40 0 R >> 233 | stream 234 | 0.377131 0 0.019886 0.000000 0.368608 0.781250 d1 235 | 236 | endstream 237 | endobj 238 | 239 | 40 0 obj 240 | 50 241 | endobj 242 | 243 | 41 0 obj 244 | << /Length 42 0 R >> 245 | stream 246 | 0.260653 0 0.056108 0.000000 0.205256 0.818537 d1 247 | 248 | endstream 249 | endobj 250 | 251 | 42 0 obj 252 | 50 253 | endobj 254 | 255 | 43 0 obj 256 | << /Length 44 0 R >> 257 | stream 258 | 0.363636 0 0.031250 -0.007102 0.323864 0.714489 d1 259 | 260 | endstream 261 | endobj 262 | 263 | 44 0 obj 264 | 51 265 | endobj 266 | 267 | 45 0 obj 268 | << /Length 46 0 R >> 269 | stream 270 | 0.624645 0 0.066051 -0.204545 0.580966 0.823153 d1 271 | 272 | endstream 273 | endobj 274 | 275 | 46 0 obj 276 | 51 277 | endobj 278 | 279 | 47 0 obj 280 | << /Length 48 0 R >> 281 | stream 282 | 0.360795 0 0.025568 0.000000 0.346591 0.786932 d1 283 | 284 | endstream 285 | endobj 286 | 287 | 48 0 obj 288 | 50 289 | endobj 290 | 291 | 49 0 obj 292 | << /Length 50 0 R >> 293 | stream 294 | 0.592685 0 0.042614 -0.010653 0.550071 0.605824 d1 295 | 296 | endstream 297 | endobj 298 | 299 | 50 0 obj 300 | 51 301 | endobj 302 | 303 | 51 0 obj 304 | << /Length 52 0 R >> 305 | stream 306 | 0.275568 0 0.073864 -0.005682 0.201705 0.201705 d1 307 | 308 | endstream 309 | endobj 310 | 311 | 52 0 obj 312 | 51 313 | endobj 314 | 315 | 53 0 obj 316 | << /Length 54 0 R >> 317 | stream 318 | 0.582386 0 0.051136 -0.011364 0.531250 0.615057 d1 319 | 320 | endstream 321 | endobj 322 | 323 | 54 0 obj 324 | 51 325 | endobj 326 | 327 | 55 0 obj 328 | << /Length 56 0 R >> 329 | stream 330 | 0.704545 0 0.022017 0.000000 0.682528 0.749290 d1 331 | 332 | endstream 333 | endobj 334 | 335 | 56 0 obj 336 | 50 337 | endobj 338 | 339 | 57 0 obj 340 | << /Length 58 0 R >> 341 | stream 342 | 0.609375 0 0.076705 -0.204545 0.558239 0.833807 d1 343 | 344 | endstream 345 | endobj 346 | 347 | 58 0 obj 348 | 51 349 | endobj 350 | 351 | 59 0 obj 352 | << /Length 60 0 R >> 353 | stream 354 | 0.522727 0 0.052557 -0.011364 0.473011 0.616477 d1 355 | 356 | endstream 357 | endobj 358 | 359 | 60 0 obj 360 | 51 361 | endobj 362 | 363 | 61 0 obj 364 | << /Length 62 0 R >> 365 | stream 366 | 0.585227 0 0.076705 0.000000 0.508523 0.629261 d1 367 | 368 | endstream 369 | endobj 370 | 371 | 62 0 obj 372 | 50 373 | endobj 374 | 375 | 63 0 obj 376 | << /Length 64 0 R >> 377 | stream 378 | 0.544034 0 0.076705 0.000000 0.529830 0.803977 d1 379 | 380 | endstream 381 | endobj 382 | 383 | 64 0 obj 384 | 50 385 | endobj 386 | 387 | 65 0 obj 388 | << /Length 66 0 R >> 389 | stream 390 | 0.372159 0 0.076705 0.000000 0.349432 0.630682 d1 391 | 392 | endstream 393 | endobj 394 | 395 | 66 0 obj 396 | 50 397 | endobj 398 | 399 | 67 0 obj 400 | [ 0.609730 0.897727 0.575994 0.574574 0.643466 0.608310 0.704545 0.607955 0.379972 0.248224 0.396662 0.377131 0.260653 0.624645 0.592685 0.577415 0.650568 0.275568 0.556818 0.582386 0.869318 0.363636 0.360795 0.522727 0.281250 0.585227 0.544034 0.563920 0.590909 0.372159 0.596591 0.609375 0.642045 ] 401 | endobj 402 | 403 | 68 0 obj 404 | << /Length 69 0 R >> 405 | stream 406 | /CIDInit /ProcSet findresource begin 407 | 12 dict begin 408 | begincmap 409 | /CIDSystemInfo 410 | << /Registry (FigmaPDF) 411 | /Ordering (FigmaPDF) 412 | /Supplement 0 413 | >> def 414 | /CMapName /A-B-C def 415 | /CMapType 2 def 416 | 1 begincodespacerange 417 | <00> 418 | endcodespacerange 419 | 1 beginbfchar 420 | <00> <006E> 421 | endbfchar 422 | 1 beginbfchar 423 | <01> <006D> 424 | endbfchar 425 | 1 beginbfchar 426 | <02> <0079> 427 | endbfchar 428 | 1 beginbfchar 429 | <03> <0061> 430 | endbfchar 431 | 1 beginbfchar 432 | <04> <0050> 433 | endbfchar 434 | 1 beginbfchar 435 | <05> <0075> 436 | endbfchar 437 | 1 beginbfchar 438 | <06> <0059> 439 | endbfchar 440 | 1 beginbfchar 441 | <07> <006F> 442 | endbfchar 443 | 1 beginbfchar 444 | <08> <0074> 445 | endbfchar 446 | 1 beginbfchar 447 | <09> <0020> 448 | endbfchar 449 | 1 beginbfchar 450 | <0A> <0072> 451 | endbfchar 452 | 1 beginbfchar 453 | <0B> <0066> 454 | endbfchar 455 | 1 beginbfchar 456 | <0C> <0069> 457 | endbfchar 458 | 1 beginbfchar 459 | <0D> <0070> 460 | endbfchar 461 | 1 beginbfchar 462 | <0E> <0065> 463 | endbfchar 464 | 1 beginbfchar 465 | <0F> <0063> 466 | endbfchar 467 | 1 beginbfchar 468 | <10> <0052> 469 | endbfchar 470 | 1 beginbfchar 471 | <11> <002E> 472 | endbfchar 473 | 1 beginbfchar 474 | <12> <0079> 475 | endbfchar 476 | 1 beginbfchar 477 | <13> <0065> 478 | endbfchar 479 | 1 beginbfchar 480 | <14> <006D> 481 | endbfchar 482 | 1 beginbfchar 483 | <15> <0074> 484 | endbfchar 485 | 1 beginbfchar 486 | <16> <0066> 487 | endbfchar 488 | 1 beginbfchar 489 | <17> <0073> 490 | endbfchar 491 | 1 beginbfchar 492 | <18> <0020> 493 | endbfchar 494 | 1 beginbfchar 495 | <19> <006E> 496 | endbfchar 497 | 1 beginbfchar 498 | <1A> <006B> 499 | endbfchar 500 | 1 beginbfchar 501 | <1B> <0061> 502 | endbfchar 503 | 1 beginbfchar 504 | <1C> <0068> 505 | endbfchar 506 | 1 beginbfchar 507 | <1D> <0072> 508 | endbfchar 509 | 1 beginbfchar 510 | <1E> <006F> 511 | endbfchar 512 | 1 beginbfchar 513 | <1F> <0070> 514 | endbfchar 515 | 1 beginbfchar 516 | <20> <0054> 517 | endbfchar 518 | endcmap 519 | CMapName currentdict /CMap defineresource pop 520 | end 521 | end 522 | endstream 523 | endobj 524 | 525 | 69 0 obj 526 | 1484 527 | endobj 528 | 529 | 70 0 obj 530 | << /Subtype /Type3 531 | /CharProcs << /C18 3 0 R 532 | /C3 1 0 R 533 | /C2 7 0 R 534 | /C30 11 0 R 535 | /C4 9 0 R 536 | /C11 39 0 R 537 | /C9 5 0 R 538 | /C0 13 0 R 539 | /C24 15 0 R 540 | /C27 19 0 R 541 | /C16 17 0 R 542 | /C7 21 0 R 543 | /C28 23 0 R 544 | /C12 41 0 R 545 | /C21 43 0 R 546 | /C5 25 0 R 547 | /C20 27 0 R 548 | /C15 29 0 R 549 | /C32 31 0 R 550 | /C8 33 0 R 551 | /C1 35 0 R 552 | /C10 37 0 R 553 | /C13 45 0 R 554 | /C22 47 0 R 555 | /C14 49 0 R 556 | /C17 51 0 R 557 | /C19 53 0 R 558 | /C6 55 0 R 559 | /C31 57 0 R 560 | /C23 59 0 R 561 | /C25 61 0 R 562 | /C26 63 0 R 563 | /C29 65 0 R 564 | >> 565 | /Encoding << /Type /Encoding 566 | /Differences [ 0 /C0 /C1 /C2 /C3 /C4 /C5 /C6 /C7 /C8 /C9 /C10 /C11 /C12 /C13 /C14 /C15 /C16 /C17 /C18 /C19 /C20 /C21 /C22 /C23 /C24 /C25 /C26 /C27 /C28 /C29 /C30 /C31 /C32 ] 567 | >> 568 | /Widths 67 0 R 569 | /FontBBox [ 0.000000 0.000000 0.000000 0.000000 ] 570 | /FontMatrix [ 1.000000 0.000000 0.000000 1.000000 0.000000 0.000000 ] 571 | /Type /Font 572 | /ToUnicode 68 0 R 573 | /FirstChar 0 574 | /LastChar 32 575 | /Resources << >> 576 | >> 577 | endobj 578 | 579 | 71 0 obj 580 | << /Length 72 0 R 581 | /Range [ 0.000000 1.000000 ] 582 | /Domain [ 0.000000 1.000000 ] 583 | /FunctionType 4 584 | >> 585 | stream 586 | { 1.000000 exch dup 0.000000 gt { exch pop dup 0.000000 sub -1.000000 mul 1.000000 add exch } if dup 1.000000 gt { exch pop 0.000000 exch } if pop } 587 | endstream 588 | endobj 589 | 590 | 72 0 obj 591 | 149 592 | endobj 593 | 594 | 73 0 obj 595 | << /BBox [ 0.000000 0.000000 500.000000 700.000000 ] 596 | /Resources << /Pattern << /P1 << /Matrix [ 35.064983 -28.864431 28.864431 35.064983 43.777412 595.760620 ] 597 | /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ] 598 | /ColorSpace /DeviceGray 599 | /Function 71 0 R 600 | /Domain [ 0.000000 1.000000 ] 601 | /ShadingType 2 602 | /Extend [ true true ] 603 | >> 604 | /PatternType 2 605 | /Type /Pattern 606 | >> >> >> 607 | /Subtype /Form 608 | /Length 74 0 R 609 | /Group << /Type /Group 610 | /S /Transparency 611 | /CS /DeviceGray 612 | >> 613 | /Type /XObject 614 | >> 615 | stream 616 | /DeviceGray CS 617 | /DeviceGray cs 618 | 1.000000 0.000000 -0.000000 1.000000 71.476868 612.707703 cm 619 | 0.000000 -0.000017 m 620 | 0.000000 19.076904 l 621 | 7.247755 19.076904 l 622 | 8.732555 19.076904 9.978277 18.822321 10.984923 18.313089 c 623 | 11.997847 17.803858 12.762276 17.089705 13.278153 16.170628 c 624 | 13.800368 15.257766 14.061415 14.192782 14.061415 12.975613 c 625 | 14.061415 11.752291 13.797171 10.690382 13.268740 9.789951 c 626 | 12.746524 8.895737 11.975815 8.203305 10.956615 7.712721 c 627 | 9.937353 7.228352 8.685353 6.986135 7.200614 6.986135 c 628 | 2.038462 6.986135 l 629 | 2.038462 9.855121 l 630 | 6.728738 9.855121 l 631 | 7.596922 9.855121 8.307877 9.973151 8.861539 10.209089 c 632 | 9.415200 10.451304 9.824122 10.802135 10.088368 11.261703 c 633 | 10.358891 11.727427 10.494154 12.298752 10.494154 12.975613 c 634 | 10.494154 13.652536 10.358891 14.230074 10.088368 14.708227 c 635 | 9.817845 15.192596 9.405723 15.558997 8.852122 15.807366 c 636 | 8.298461 16.061951 7.584368 16.189274 6.709845 16.189274 c 637 | 3.501231 16.189274 l 638 | 3.501231 -0.000017 l 639 | 0.000000 -0.000017 l 640 | h 641 | 9.984555 8.644228 m 642 | 14.769230 -0.000017 l 643 | 10.862215 -0.000017 l 644 | 6.162524 8.644228 l 645 | 9.984555 8.644228 l 646 | h 647 | /Pattern cs 648 | /P1 scn 649 | f 650 | n 651 | 652 | endstream 653 | endobj 654 | 655 | 74 0 obj 656 | 1128 657 | endobj 658 | 659 | 75 0 obj 660 | << /Length 76 0 R 661 | /Range [ 0.000000 1.000000 ] 662 | /Domain [ 0.000000 1.000000 ] 663 | /FunctionType 4 664 | >> 665 | stream 666 | { 0.600000 exch dup 0.000000 gt { exch pop dup 0.000000 sub -0.400000 mul 0.600000 add exch } if dup 1.000000 gt { exch pop 0.200000 exch } if pop } 667 | endstream 668 | endobj 669 | 670 | 76 0 obj 671 | 149 672 | endobj 673 | 674 | 77 0 obj 675 | << /BBox [ 0.000000 0.000000 500.000000 700.000000 ] 676 | /Resources << /Pattern << /P1 << /Matrix [ 0.000000 -44.307686 44.307686 0.000000 9.692314 644.153870 ] 677 | /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ] 678 | /ColorSpace /DeviceGray 679 | /Function 75 0 R 680 | /Domain [ 0.000000 1.000000 ] 681 | /ShadingType 2 682 | /Extend [ true true ] 683 | >> 684 | /PatternType 2 685 | /Type /Pattern 686 | >> >> >> 687 | /Subtype /Form 688 | /Length 78 0 R 689 | /Group << /Type /Group 690 | /S /Transparency 691 | /CS /DeviceGray 692 | >> 693 | /Type /XObject 694 | >> 695 | stream 696 | /DeviceGray CS 697 | /DeviceGray cs 698 | 1.000000 0.000000 -0.000000 1.000000 54.000000 598.000000 cm 699 | 30.481234 48.000000 m 700 | 33.169601 48.000015 35.300064 48.000027 37.017292 47.859722 c 701 | 38.773293 47.716248 40.259140 47.416904 41.615879 46.725613 c 702 | 43.815941 45.604633 45.604618 43.815937 46.725601 41.615875 c 703 | 47.416924 40.259136 47.716251 38.773293 47.859695 37.017292 c 704 | 48.000004 35.300060 48.000000 33.169601 48.000000 30.481293 c 705 | 48.000000 17.518705 l 706 | 48.000000 14.830399 48.000004 12.699936 47.859695 10.982708 c 707 | 47.716251 9.226707 47.416924 7.740860 46.725601 6.384121 c 708 | 45.604618 4.184059 43.815941 2.395382 41.615879 1.274399 c 709 | 40.259140 0.583076 38.773293 0.283749 37.017292 0.140305 c 710 | 35.300064 -0.000004 33.169601 0.000000 30.481295 0.000000 c 711 | 17.518707 0.000000 l 712 | 14.830400 0.000000 12.699940 -0.000004 10.982709 0.140305 c 713 | 9.226708 0.283749 7.740862 0.583076 6.384123 1.274399 c 714 | 4.184062 2.395382 2.395366 4.184059 1.274388 6.384121 c 715 | 0.583094 7.740860 0.283751 9.226707 0.140278 10.982708 c 716 | -0.000025 12.699936 -0.000014 14.830399 0.000000 17.518766 c 717 | 0.000000 30.481230 l 718 | -0.000014 33.169601 -0.000025 35.300060 0.140278 37.017292 c 719 | 0.283751 38.773293 0.583094 40.259136 1.274388 41.615875 c 720 | 2.395366 43.815937 4.184062 45.604633 6.384123 46.725613 c 721 | 7.740862 47.416908 9.226708 47.716248 10.982709 47.859722 c 722 | 12.699940 48.000027 14.830400 48.000015 17.518770 48.000000 c 723 | 30.481234 48.000000 l 724 | h 725 | 2.919323 40.777721 m 726 | 1.846154 38.671570 1.846154 35.914337 1.846154 30.400000 c 727 | 1.846154 17.599998 l 728 | 1.846154 12.085659 1.846154 9.328430 2.919323 7.222275 c 729 | 3.863305 5.369598 5.369576 3.863319 7.222277 2.919319 c 730 | 9.328431 1.846149 12.085662 1.846153 17.600000 1.846153 c 731 | 30.400002 1.846153 l 732 | 35.914341 1.846153 38.671570 1.846149 40.777725 2.919319 c 733 | 42.630402 3.863319 44.136681 5.369598 45.080681 7.222275 c 734 | 46.153851 9.328430 46.153847 12.085659 46.153847 17.599998 c 735 | 46.153847 30.400000 l 736 | 46.153847 35.914337 46.153851 38.671570 45.080681 40.777721 c 737 | 44.136681 42.630424 42.630402 44.136696 40.777725 45.080677 c 738 | 38.671570 46.153847 35.914341 46.153847 30.400002 46.153847 c 739 | 17.600000 46.153847 l 740 | 12.085662 46.153847 9.328431 46.153847 7.222277 45.080677 c 741 | 5.369576 44.136696 3.863305 42.630424 2.919323 40.777721 c 742 | h 743 | /Pattern cs 744 | /P1 scn 745 | f* 746 | n 747 | 748 | endstream 749 | endobj 750 | 751 | 78 0 obj 752 | 2249 753 | endobj 754 | 755 | 79 0 obj 756 | << /Length 80 0 R 757 | /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] 758 | /Domain [ 0.000000 1.000000 ] 759 | /FunctionType 4 760 | >> 761 | stream 762 | { 1.000000 exch 1.000000 exch 1.000000 exch dup 0.000000 gt { exch pop exch pop exch pop dup 0.000000 sub 0.000000 mul 1.000000 add exch dup 0.000000 sub 0.000000 mul 1.000000 add exch dup 0.000000 sub 0.000000 mul 1.000000 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 1.000000 exch 1.000000 exch 1.000000 exch } if pop } 763 | endstream 764 | endobj 765 | 766 | 80 0 obj 767 | 336 768 | endobj 769 | 770 | 81 0 obj 771 | << /Length 82 0 R 772 | /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] 773 | /Domain [ 0.000000 1.000000 ] 774 | /FunctionType 4 775 | >> 776 | stream 777 | { 1.000000 exch 1.000000 exch 1.000000 exch dup 0.000000 gt { exch pop exch pop exch pop dup 0.000000 sub 0.000000 mul 1.000000 add exch dup 0.000000 sub 0.000000 mul 1.000000 add exch dup 0.000000 sub 0.000000 mul 1.000000 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 1.000000 exch 1.000000 exch 1.000000 exch } if pop } 778 | endstream 779 | endobj 780 | 781 | 82 0 obj 782 | 336 783 | endobj 784 | 785 | 83 0 obj 786 | << /Length 84 0 R 787 | /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] 788 | /Domain [ 0.000000 1.000000 ] 789 | /FunctionType 4 790 | >> 791 | stream 792 | { 0.298039 exch 0.298039 exch 0.341176 exch dup 0.000000 gt { exch pop exch pop exch pop dup 0.000000 sub -0.626471 mul 0.298039 add exch dup 0.000000 sub -0.626471 mul 0.298039 add exch dup 0.000000 sub -0.679412 mul 0.341176 add exch } if dup 0.444444 gt { exch pop exch pop exch pop dup 0.444444 sub -0.035294 mul 0.019608 add exch dup 0.444444 sub -0.035294 mul 0.019608 add exch dup 0.444444 sub -0.070588 mul 0.039216 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 0.000000 exch 0.000000 exch 0.000000 exch } if pop } 793 | endstream 794 | endobj 795 | 796 | 84 0 obj 797 | 536 798 | endobj 799 | 800 | 85 0 obj 801 | << /Length 86 0 R 802 | /Range [ 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 ] 803 | /Domain [ 0.000000 1.000000 ] 804 | /FunctionType 4 805 | >> 806 | stream 807 | { 0.298039 exch 0.298039 exch 0.341176 exch dup 0.000000 gt { exch pop exch pop exch pop dup 0.000000 sub -0.626471 mul 0.298039 add exch dup 0.000000 sub -0.626471 mul 0.298039 add exch dup 0.000000 sub -0.679412 mul 0.341176 add exch } if dup 0.444444 gt { exch pop exch pop exch pop dup 0.444444 sub -0.035294 mul 0.019608 add exch dup 0.444444 sub -0.035294 mul 0.019608 add exch dup 0.444444 sub -0.070588 mul 0.039216 add exch } if dup 1.000000 gt { exch pop exch pop exch pop 0.000000 exch 0.000000 exch 0.000000 exch } if pop } 808 | endstream 809 | endobj 810 | 811 | 86 0 obj 812 | 536 813 | endobj 814 | 815 | 87 0 obj 816 | << /Font << /F1 70 0 R >> 817 | /ExtGState << /E2 << /SMask << /Type /Mask 818 | /G 73 0 R 819 | /S /Luminosity 820 | >> 821 | /Type /ExtGState 822 | >> 823 | /E1 << /SMask << /Type /Mask 824 | /G 77 0 R 825 | /S /Luminosity 826 | >> 827 | /Type /ExtGState 828 | >> 829 | >> 830 | /Pattern << /P4 << /Matrix [ 35.064983 -28.864431 28.864431 35.064983 43.777412 595.760620 ] 831 | /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ] 832 | /ColorSpace /DeviceRGB 833 | /Function 79 0 R 834 | /Domain [ 0.000000 1.000000 ] 835 | /ShadingType 2 836 | /Extend [ true true ] 837 | >> 838 | /PatternType 2 839 | /Type /Pattern 840 | >> 841 | /P3 << /Matrix [ 0.000000 -44.307686 44.307686 0.000000 9.692314 644.153870 ] 842 | /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ] 843 | /ColorSpace /DeviceRGB 844 | /Function 81 0 R 845 | /Domain [ 0.000000 1.000000 ] 846 | /ShadingType 2 847 | /Extend [ true true ] 848 | >> 849 | /PatternType 2 850 | /Type /Pattern 851 | >> 852 | /P2 << /Matrix [ 0.000000 -58.461540 58.461540 0.000000 -2.615410 658.307739 ] 853 | /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ] 854 | /ColorSpace /DeviceRGB 855 | /Function 83 0 R 856 | /Domain [ 0.000000 1.000000 ] 857 | /ShadingType 2 858 | /Extend [ true true ] 859 | >> 860 | /PatternType 2 861 | /Type /Pattern 862 | >> 863 | /P1 << /Matrix [ 0.000000 -58.461540 58.461540 0.000000 -2.615410 658.307739 ] 864 | /Shading << /Coords [ 0.000000 0.000000 1.000000 0.000000 ] 865 | /ColorSpace /DeviceRGB 866 | /Function 85 0 R 867 | /Domain [ 0.000000 1.000000 ] 868 | /ShadingType 2 869 | /Extend [ true true ] 870 | >> 871 | /PatternType 2 872 | /Type /Pattern 873 | >> 874 | >> 875 | >> 876 | endobj 877 | 878 | 88 0 obj 879 | << /Length 89 0 R >> 880 | stream 881 | /DeviceRGB CS 882 | /DeviceRGB cs 883 | q 884 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 885 | 1.000000 1.000000 1.000000 scn 886 | 0.000000 700.000000 m 887 | 500.000000 700.000000 l 888 | 500.000000 0.000000 l 889 | 0.000000 0.000000 l 890 | 0.000000 700.000000 l 891 | h 892 | f 893 | n 894 | Q 895 | q 896 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 897 | 0.000000 0.000000 0.000000 scn 898 | 0.000000 700.000000 m 899 | 500.000000 700.000000 l 900 | 500.000000 0.000000 l 901 | 0.000000 0.000000 l 902 | 0.000000 700.000000 l 903 | h 904 | f 905 | n 906 | Q 907 | q 908 | 1.000000 0.000000 -0.000000 1.000000 55.846130 599.846191 cm 909 | /Pattern cs 910 | /P1 scn 911 | 0.000000 28.553831 m 912 | 0.000000 34.068169 -0.000000 36.825401 1.073169 38.931557 c 913 | 2.017151 40.784256 3.523422 42.290527 5.376123 43.234509 c 914 | 7.482277 44.307678 10.239509 44.307678 15.753847 44.307678 c 915 | 28.553848 44.307678 l 916 | 34.068188 44.307678 36.825417 44.307678 38.931572 43.234509 c 917 | 40.784248 42.290527 42.290527 40.784256 43.234528 38.931557 c 918 | 44.307697 36.825401 44.307693 34.068169 44.307693 28.553831 c 919 | 44.307693 15.753830 l 920 | 44.307693 10.239491 44.307697 7.482262 43.234528 5.376106 c 921 | 42.290527 3.523430 40.784248 2.017151 38.931572 1.073151 c 922 | 36.825417 -0.000019 34.068188 -0.000015 28.553848 -0.000015 c 923 | 15.753847 -0.000015 l 924 | 10.239509 -0.000015 7.482277 -0.000019 5.376123 1.073151 c 925 | 3.523422 2.017151 2.017151 3.523430 1.073169 5.376106 c 926 | -0.000000 7.482262 0.000000 10.239491 0.000000 15.753830 c 927 | 0.000000 28.553831 l 928 | h 929 | f 930 | n 931 | Q 932 | q 933 | 1.000000 0.000000 -0.000000 1.000000 55.846130 599.846191 cm 934 | 0.000000 0.000000 0.000000 scn 935 | 28.553848 42.461525 m 936 | 15.753847 42.461525 l 937 | 12.966216 42.461525 10.986524 42.460091 9.437232 42.333511 c 938 | 7.909539 42.208694 6.963692 41.971451 6.214215 41.589573 c 939 | 4.708923 40.822590 3.485090 39.598755 2.718105 38.093464 c 940 | 2.336228 37.343987 2.098985 36.398140 1.974166 34.870445 c 941 | 1.847588 33.321156 1.846154 31.341461 1.846154 28.553831 c 942 | 1.846154 15.753830 l 943 | 1.846154 12.966200 1.847588 10.986507 1.974166 9.437214 c 944 | 2.098985 7.909523 2.336228 6.963676 2.718105 6.214199 c 945 | 3.485090 4.708908 4.708923 3.485092 6.214215 2.718079 c 946 | 6.963692 2.336231 7.909539 2.098999 9.437232 1.974140 c 947 | 10.986524 1.847553 12.966216 1.846138 15.753847 1.846138 c 948 | 28.553848 1.846138 l 949 | 31.341478 1.846138 33.321171 1.847553 34.870464 1.974140 c 950 | 36.398155 2.098999 37.344002 2.336231 38.093479 2.718079 c 951 | 39.598770 3.485092 40.822586 4.708908 41.589600 6.214199 c 952 | 41.971447 6.963676 42.208679 7.909523 42.333538 9.437214 c 953 | 42.460125 10.986507 42.461540 12.966200 42.461540 15.753830 c 954 | 42.461540 28.553831 l 955 | 42.461540 31.341461 42.460125 33.321156 42.333538 34.870445 c 956 | 42.208679 36.398140 41.971447 37.343987 41.589600 38.093464 c 957 | 40.822586 39.598755 39.598770 40.822590 38.093479 41.589573 c 958 | 37.344002 41.971451 36.398155 42.208694 34.870464 42.333511 c 959 | 33.321171 42.460091 31.341478 42.461525 28.553848 42.461525 c 960 | h 961 | 1.073169 38.931557 m 962 | -0.000000 36.825401 0.000000 34.068169 0.000000 28.553831 c 963 | 0.000000 15.753830 l 964 | 0.000000 10.239491 -0.000000 7.482262 1.073169 5.376106 c 965 | 2.017151 3.523430 3.523422 2.017151 5.376123 1.073151 c 966 | 7.482277 -0.000019 10.239509 -0.000015 15.753847 -0.000015 c 967 | 28.553848 -0.000015 l 968 | 34.068188 -0.000015 36.825417 -0.000019 38.931572 1.073151 c 969 | 40.784248 2.017151 42.290527 3.523430 43.234528 5.376106 c 970 | 44.307697 7.482262 44.307693 10.239491 44.307693 15.753830 c 971 | 44.307693 28.553831 l 972 | 44.307693 34.068169 44.307697 36.825401 43.234528 38.931557 c 973 | 42.290527 40.784256 40.784248 42.290527 38.931572 43.234509 c 974 | 36.825417 44.307678 34.068188 44.307678 28.553848 44.307678 c 975 | 15.753847 44.307678 l 976 | 10.239509 44.307678 7.482277 44.307678 5.376123 43.234509 c 977 | 3.523422 42.290527 2.017151 40.784256 1.073169 38.931557 c 978 | h 979 | f* 980 | n 981 | Q 982 | q 983 | 1.000000 0.000000 -0.000000 1.000000 55.846130 599.846191 cm 984 | /Pattern cs 985 | /P2 scn 986 | 0.000000 28.553831 m 987 | 0.000000 34.068169 -0.000000 36.825401 1.073169 38.931557 c 988 | 2.017151 40.784256 3.523422 42.290527 5.376123 43.234509 c 989 | 7.482277 44.307678 10.239509 44.307678 15.753847 44.307678 c 990 | 28.553848 44.307678 l 991 | 34.068188 44.307678 36.825417 44.307678 38.931572 43.234509 c 992 | 40.784248 42.290527 42.290527 40.784256 43.234528 38.931557 c 993 | 44.307697 36.825401 44.307693 34.068169 44.307693 28.553831 c 994 | 44.307693 15.753830 l 995 | 44.307693 10.239491 44.307697 7.482262 43.234528 5.376106 c 996 | 42.290527 3.523430 40.784248 2.017151 38.931572 1.073151 c 997 | 36.825417 -0.000019 34.068188 -0.000015 28.553848 -0.000015 c 998 | 15.753847 -0.000015 l 999 | 10.239509 -0.000015 7.482277 -0.000019 5.376123 1.073151 c 1000 | 3.523422 2.017151 2.017151 3.523430 1.073169 5.376106 c 1001 | -0.000000 7.482262 0.000000 10.239491 0.000000 15.753830 c 1002 | 0.000000 28.553831 l 1003 | h 1004 | f 1005 | n 1006 | Q 1007 | q 1008 | 1.000000 0.000000 -0.000000 1.000000 54.000000 598.000000 cm 1009 | 0.000000 0.000000 0.000000 scn 1010 | 30.481234 48.000000 m 1011 | 33.169601 48.000015 35.300064 48.000027 37.017292 47.859722 c 1012 | 38.773293 47.716248 40.259140 47.416904 41.615879 46.725613 c 1013 | 43.815941 45.604633 45.604618 43.815937 46.725601 41.615875 c 1014 | 47.416924 40.259136 47.716251 38.773293 47.859695 37.017292 c 1015 | 48.000004 35.300060 48.000000 33.169601 48.000000 30.481293 c 1016 | 48.000000 17.518705 l 1017 | 48.000000 14.830399 48.000004 12.699936 47.859695 10.982708 c 1018 | 47.716251 9.226707 47.416924 7.740860 46.725601 6.384121 c 1019 | 45.604618 4.184059 43.815941 2.395382 41.615879 1.274399 c 1020 | 40.259140 0.583076 38.773293 0.283749 37.017292 0.140305 c 1021 | 35.300064 -0.000004 33.169601 0.000000 30.481295 0.000000 c 1022 | 17.518707 0.000000 l 1023 | 14.830400 0.000000 12.699940 -0.000004 10.982709 0.140305 c 1024 | 9.226708 0.283749 7.740862 0.583076 6.384123 1.274399 c 1025 | 4.184062 2.395382 2.395366 4.184059 1.274388 6.384121 c 1026 | 0.583094 7.740860 0.283751 9.226707 0.140278 10.982708 c 1027 | -0.000025 12.699936 -0.000014 14.830399 0.000000 17.518766 c 1028 | 0.000000 30.481230 l 1029 | -0.000014 33.169601 -0.000025 35.300060 0.140278 37.017292 c 1030 | 0.283751 38.773293 0.583094 40.259136 1.274388 41.615875 c 1031 | 2.395366 43.815937 4.184062 45.604633 6.384123 46.725613 c 1032 | 7.740862 47.416908 9.226708 47.716248 10.982709 47.859722 c 1033 | 12.699940 48.000027 14.830400 48.000015 17.518770 48.000000 c 1034 | 30.481234 48.000000 l 1035 | h 1036 | 2.919323 40.777721 m 1037 | 1.846154 38.671570 1.846154 35.914337 1.846154 30.400000 c 1038 | 1.846154 17.599998 l 1039 | 1.846154 12.085659 1.846154 9.328430 2.919323 7.222275 c 1040 | 3.863305 5.369598 5.369576 3.863319 7.222277 2.919319 c 1041 | 9.328431 1.846149 12.085662 1.846153 17.600000 1.846153 c 1042 | 30.400002 1.846153 l 1043 | 35.914341 1.846153 38.671570 1.846149 40.777725 2.919319 c 1044 | 42.630402 3.863319 44.136681 5.369598 45.080681 7.222275 c 1045 | 46.153851 9.328430 46.153847 12.085659 46.153847 17.599998 c 1046 | 46.153847 30.400000 l 1047 | 46.153847 35.914337 46.153851 38.671570 45.080681 40.777721 c 1048 | 44.136681 42.630424 42.630402 44.136696 40.777725 45.080677 c 1049 | 38.671570 46.153847 35.914341 46.153847 30.400002 46.153847 c 1050 | 17.600000 46.153847 l 1051 | 12.085662 46.153847 9.328431 46.153847 7.222277 45.080677 c 1052 | 5.369576 44.136696 3.863305 42.630424 2.919323 40.777721 c 1053 | h 1054 | f* 1055 | n 1056 | Q 1057 | q 1058 | /E1 gs 1059 | 1.000000 0.000000 -0.000000 1.000000 54.000000 598.000000 cm 1060 | /Pattern cs 1061 | /P3 scn 1062 | 30.481234 48.000000 m 1063 | 33.169601 48.000015 35.300064 48.000027 37.017292 47.859722 c 1064 | 38.773293 47.716248 40.259140 47.416904 41.615879 46.725613 c 1065 | 43.815941 45.604633 45.604618 43.815937 46.725601 41.615875 c 1066 | 47.416924 40.259136 47.716251 38.773293 47.859695 37.017292 c 1067 | 48.000004 35.300060 48.000000 33.169601 48.000000 30.481293 c 1068 | 48.000000 17.518705 l 1069 | 48.000000 14.830399 48.000004 12.699936 47.859695 10.982708 c 1070 | 47.716251 9.226707 47.416924 7.740860 46.725601 6.384121 c 1071 | 45.604618 4.184059 43.815941 2.395382 41.615879 1.274399 c 1072 | 40.259140 0.583076 38.773293 0.283749 37.017292 0.140305 c 1073 | 35.300064 -0.000004 33.169601 0.000000 30.481295 0.000000 c 1074 | 17.518707 0.000000 l 1075 | 14.830400 0.000000 12.699940 -0.000004 10.982709 0.140305 c 1076 | 9.226708 0.283749 7.740862 0.583076 6.384123 1.274399 c 1077 | 4.184062 2.395382 2.395366 4.184059 1.274388 6.384121 c 1078 | 0.583094 7.740860 0.283751 9.226707 0.140278 10.982708 c 1079 | -0.000025 12.699936 -0.000014 14.830399 0.000000 17.518766 c 1080 | 0.000000 30.481230 l 1081 | -0.000014 33.169601 -0.000025 35.300060 0.140278 37.017292 c 1082 | 0.283751 38.773293 0.583094 40.259136 1.274388 41.615875 c 1083 | 2.395366 43.815937 4.184062 45.604633 6.384123 46.725613 c 1084 | 7.740862 47.416908 9.226708 47.716248 10.982709 47.859722 c 1085 | 12.699940 48.000027 14.830400 48.000015 17.518770 48.000000 c 1086 | 30.481234 48.000000 l 1087 | h 1088 | 2.919323 40.777721 m 1089 | 1.846154 38.671570 1.846154 35.914337 1.846154 30.400000 c 1090 | 1.846154 17.599998 l 1091 | 1.846154 12.085659 1.846154 9.328430 2.919323 7.222275 c 1092 | 3.863305 5.369598 5.369576 3.863319 7.222277 2.919319 c 1093 | 9.328431 1.846149 12.085662 1.846153 17.600000 1.846153 c 1094 | 30.400002 1.846153 l 1095 | 35.914341 1.846153 38.671570 1.846149 40.777725 2.919319 c 1096 | 42.630402 3.863319 44.136681 5.369598 45.080681 7.222275 c 1097 | 46.153851 9.328430 46.153847 12.085659 46.153847 17.599998 c 1098 | 46.153847 30.400000 l 1099 | 46.153847 35.914337 46.153851 38.671570 45.080681 40.777721 c 1100 | 44.136681 42.630424 42.630402 44.136696 40.777725 45.080677 c 1101 | 38.671570 46.153847 35.914341 46.153847 30.400002 46.153847 c 1102 | 17.600000 46.153847 l 1103 | 12.085662 46.153847 9.328431 46.153847 7.222277 45.080677 c 1104 | 5.369576 44.136696 3.863305 42.630424 2.919323 40.777721 c 1105 | h 1106 | f* 1107 | n 1108 | Q 1109 | q 1110 | /E2 gs 1111 | 1.000000 0.000000 -0.000000 1.000000 71.476868 612.707703 cm 1112 | /Pattern cs 1113 | /P4 scn 1114 | 0.000000 -0.000017 m 1115 | 0.000000 19.076904 l 1116 | 7.247755 19.076904 l 1117 | 8.732555 19.076904 9.978277 18.822321 10.984923 18.313089 c 1118 | 11.997847 17.803858 12.762276 17.089705 13.278153 16.170628 c 1119 | 13.800368 15.257766 14.061415 14.192782 14.061415 12.975613 c 1120 | 14.061415 11.752291 13.797171 10.690382 13.268740 9.789951 c 1121 | 12.746524 8.895737 11.975815 8.203305 10.956615 7.712721 c 1122 | 9.937353 7.228352 8.685353 6.986135 7.200614 6.986135 c 1123 | 2.038462 6.986135 l 1124 | 2.038462 9.855121 l 1125 | 6.728738 9.855121 l 1126 | 7.596922 9.855121 8.307877 9.973151 8.861539 10.209089 c 1127 | 9.415200 10.451304 9.824122 10.802135 10.088368 11.261703 c 1128 | 10.358891 11.727427 10.494154 12.298752 10.494154 12.975613 c 1129 | 10.494154 13.652536 10.358891 14.230074 10.088368 14.708227 c 1130 | 9.817845 15.192596 9.405723 15.558997 8.852122 15.807366 c 1131 | 8.298461 16.061951 7.584368 16.189274 6.709845 16.189274 c 1132 | 3.501231 16.189274 l 1133 | 3.501231 -0.000017 l 1134 | 0.000000 -0.000017 l 1135 | h 1136 | 9.984555 8.644228 m 1137 | 14.769230 -0.000017 l 1138 | 10.862215 -0.000017 l 1139 | 6.162524 8.644228 l 1140 | 9.984555 8.644228 l 1141 | h 1142 | f 1143 | n 1144 | Q 1145 | q 1146 | 1.000000 0.000000 -0.000000 1.000000 54.000000 543.594482 cm 1147 | 1.000000 1.000000 1.000000 scn 1148 | 0.000000 -0.594482 m 1149 | h 1150 | 1.291193 -0.594482 m 1151 | 1.291193 12.496428 l 1152 | 6.200284 12.496428 l 1153 | 7.205966 12.496428 8.049716 12.321711 8.731535 11.972280 c 1154 | 9.417615 11.622848 9.935370 11.132791 10.284801 10.502110 c 1155 | 10.638495 9.875689 10.815342 9.144864 10.815342 8.309637 c 1156 | 10.815342 7.470149 10.636365 6.741455 10.278410 6.123557 c 1157 | 9.924716 5.509921 9.402699 5.034780 8.712358 4.698132 c 1158 | 8.022017 4.365746 7.174006 4.199553 6.168324 4.199553 c 1159 | 2.671875 4.199553 l 1160 | 2.671875 6.168303 l 1161 | 5.848722 6.168303 l 1162 | 6.436790 6.168303 6.918324 6.249269 7.293324 6.411200 c 1163 | 7.668324 6.577394 7.945313 6.818160 8.124290 7.133501 c 1164 | 8.307528 7.453103 8.399148 7.845149 8.399148 8.309637 c 1165 | 8.399148 8.774125 8.307528 9.170432 8.124290 9.498558 c 1166 | 7.941051 9.830944 7.661932 10.082365 7.286932 10.252819 c 1167 | 6.911932 10.427534 6.428267 10.514893 5.835938 10.514893 c 1168 | 3.662642 10.514893 l 1169 | 3.662642 -0.594482 l 1170 | 1.291193 -0.594482 l 1171 | h 1172 | 8.053977 5.337336 m 1173 | 11.294744 -0.594482 l 1174 | 8.648438 -0.594482 l 1175 | 5.465199 5.337336 l 1176 | 8.053977 5.337336 l 1177 | h 1178 | 11.566406 -0.594482 m 1179 | h 1180 | 17.101917 -0.786243 m 1181 | 16.117542 -0.786243 15.267401 -0.581697 14.551492 -0.172607 c 1182 | 13.839844 0.240744 13.292259 0.824551 12.908736 1.578814 c 1183 | 12.525213 2.337337 12.333451 3.230092 12.333451 4.257080 c 1184 | 12.333451 5.267023 12.525213 6.153387 12.908736 6.916171 c 1185 | 13.296520 7.683217 13.837713 8.279808 14.532315 8.705944 c 1186 | 15.226917 9.136342 16.042969 9.351541 16.980469 9.351541 c 1187 | 17.585583 9.351541 18.156605 9.253530 18.693537 9.057507 c 1188 | 19.234730 8.865746 19.712004 8.567450 20.125355 8.162620 c 1189 | 20.542969 7.757791 20.871094 7.242166 21.109730 6.615745 c 1190 | 21.348368 5.993586 21.467686 5.252109 21.467686 4.391314 c 1191 | 21.467686 3.681796 l 1192 | 13.420099 3.681796 l 1193 | 13.420099 5.241455 l 1194 | 19.249645 5.241455 l 1195 | 19.245384 5.684637 19.149504 6.078814 18.962004 6.423984 c 1196 | 18.774504 6.773416 18.512430 7.048273 18.175781 7.248557 c 1197 | 17.843395 7.448841 17.455610 7.548984 17.012428 7.548984 c 1198 | 16.539417 7.548984 16.123936 7.433927 15.765981 7.203814 c 1199 | 15.408026 6.977962 15.128906 6.679666 14.928622 6.308928 c 1200 | 14.732599 5.942450 14.632458 5.539750 14.628196 5.100830 c 1201 | 14.628196 3.739325 l 1202 | 14.628196 3.168302 14.732599 2.678245 14.941406 2.269154 c 1203 | 15.150213 1.864325 15.442117 1.553245 15.817117 1.335915 c 1204 | 16.192116 1.122847 16.631037 1.016314 17.133879 1.016314 c 1205 | 17.470526 1.016314 17.775213 1.063189 18.047941 1.156939 c 1206 | 18.320667 1.254950 18.557173 1.397705 18.757458 1.585205 c 1207 | 18.957743 1.772705 19.109020 2.004950 19.211292 2.281939 c 1208 | 21.371803 2.039041 l 1209 | 21.235441 1.468018 20.975498 0.969439 20.591976 0.543303 c 1210 | 20.212713 0.121428 19.726917 -0.206697 19.134588 -0.441072 c 1211 | 18.542259 -0.671186 17.864702 -0.786243 17.101917 -0.786243 c 1212 | h 1213 | 22.236328 -0.594482 m 1214 | h 1215 | 27.707920 -0.786243 m 1216 | 26.727806 -0.786243 25.886187 -0.571045 25.183062 -0.140648 c 1217 | 24.484198 0.289751 23.945135 0.884211 23.565874 1.642734 c 1218 | 23.190874 2.405518 23.003374 3.283359 23.003374 4.276257 c 1219 | 23.003374 5.273416 23.195135 6.153387 23.578657 6.916171 c 1220 | 23.962181 7.683217 24.503374 8.279808 25.202238 8.705944 c 1221 | 25.905363 9.136342 26.736328 9.351541 27.695135 9.351541 c 1222 | 28.492010 9.351541 29.197266 9.204524 29.810902 8.910490 c 1223 | 30.428801 8.620717 30.920988 8.209495 31.287464 7.676825 c 1224 | 31.653942 7.148416 31.862749 6.530518 31.913885 5.823132 c 1225 | 29.702238 5.823132 l 1226 | 29.612749 6.296143 29.399681 6.690319 29.063032 7.005660 c 1227 | 28.730646 7.325262 28.285334 7.485064 27.727095 7.485064 c 1228 | 27.254084 7.485064 26.838600 7.357223 26.480646 7.101541 c 1229 | 26.122692 6.850121 25.843573 6.487905 25.643288 6.014893 c 1230 | 25.447266 5.541882 25.349255 4.975121 25.349255 4.314609 c 1231 | 25.349255 3.645575 25.447266 3.070291 25.643288 2.588757 c 1232 | 25.839312 2.111484 26.114170 1.742876 26.467863 1.482932 c 1233 | 26.825817 1.227249 27.245562 1.099409 27.727095 1.099409 c 1234 | 28.068005 1.099409 28.372692 1.163330 28.641157 1.291170 c 1235 | 28.913885 1.423273 29.141869 1.612905 29.325108 1.860064 c 1236 | 29.508345 2.107223 29.634056 2.407648 29.702238 2.761342 c 1237 | 31.913885 2.761342 l 1238 | 31.858486 2.066740 31.653942 1.450973 31.300249 0.914040 c 1239 | 30.946556 0.381369 30.465021 -0.036243 29.855646 -0.338800 c 1240 | 29.246271 -0.637096 28.530363 -0.786243 27.707920 -0.786243 c 1241 | h 1242 | 32.625000 -0.594482 m 1243 | h 1244 | 38.160511 -0.786243 m 1245 | 37.176136 -0.786243 36.325993 -0.581697 35.610085 -0.172607 c 1246 | 34.898438 0.240744 34.350853 0.824551 33.967331 1.578814 c 1247 | 33.583805 2.337337 33.392044 3.230092 33.392044 4.257080 c 1248 | 33.392044 5.267023 33.583805 6.153387 33.967331 6.916171 c 1249 | 34.355114 7.683217 34.896309 8.279808 35.590908 8.705944 c 1250 | 36.285511 9.136342 37.101562 9.351541 38.039062 9.351541 c 1251 | 38.644176 9.351541 39.215199 9.253530 39.752132 9.057507 c 1252 | 40.293324 8.865746 40.770596 8.567450 41.183949 8.162620 c 1253 | 41.601562 7.757791 41.929688 7.242166 42.168324 6.615745 c 1254 | 42.406960 5.993586 42.526279 5.252109 42.526279 4.391314 c 1255 | 42.526279 3.681796 l 1256 | 34.478695 3.681796 l 1257 | 34.478695 5.241455 l 1258 | 40.308239 5.241455 l 1259 | 40.303978 5.684637 40.208096 6.078814 40.020596 6.423984 c 1260 | 39.833096 6.773416 39.571022 7.048273 39.234375 7.248557 c 1261 | 38.901989 7.448841 38.514202 7.548984 38.071022 7.548984 c 1262 | 37.598011 7.548984 37.182529 7.433927 36.824574 7.203814 c 1263 | 36.466621 6.977962 36.187500 6.679666 35.987217 6.308928 c 1264 | 35.791191 5.942450 35.691051 5.539750 35.686790 5.100830 c 1265 | 35.686790 3.739325 l 1266 | 35.686790 3.168302 35.791195 2.678245 36.000000 2.269154 c 1267 | 36.208805 1.864325 36.500710 1.553245 36.875710 1.335915 c 1268 | 37.250710 1.122847 37.689629 1.016314 38.192471 1.016314 c 1269 | 38.529121 1.016314 38.833809 1.063189 39.106533 1.156939 c 1270 | 39.379261 1.254950 39.615768 1.397705 39.816051 1.585205 c 1271 | 40.016335 1.772705 40.167614 2.004950 40.269886 2.281939 c 1272 | 42.430397 2.039041 l 1273 | 42.294033 1.468018 42.034092 0.969439 41.650570 0.543303 c 1274 | 41.271309 0.121428 40.785511 -0.206697 40.193184 -0.441072 c 1275 | 39.600853 -0.671186 38.923294 -0.786243 38.160511 -0.786243 c 1276 | h 1277 | 43.294922 -0.594482 m 1278 | h 1279 | 44.483841 -0.594482 m 1280 | 44.483841 9.223700 l 1281 | 46.797764 9.223700 l 1282 | 46.797764 -0.594482 l 1283 | 44.483841 -0.594482 l 1284 | h 1285 | 45.647194 10.617165 m 1286 | 45.280716 10.617165 44.965374 10.738614 44.701172 10.981512 c 1287 | 44.436970 11.228672 44.304867 11.524837 44.304867 11.870007 c 1288 | 44.304867 12.219439 44.436970 12.515604 44.701172 12.758501 c 1289 | 44.965374 13.005660 45.280716 13.129240 45.647194 13.129240 c 1290 | 46.017933 13.129240 46.333275 13.005660 46.593216 12.758501 c 1291 | 46.857422 12.515604 46.989525 12.219439 46.989525 11.870007 c 1292 | 46.989525 11.524837 46.857422 11.228672 46.593216 10.981512 c 1293 | 46.333275 10.738614 46.017933 10.617165 45.647194 10.617165 c 1294 | h 1295 | 47.988281 -0.594482 m 1296 | h 1297 | 49.177200 -4.276300 m 1298 | 49.177200 9.223700 l 1299 | 51.452770 9.223700 l 1300 | 51.452770 7.600120 l 1301 | 51.587002 7.600120 l 1302 | 51.706322 7.838757 51.874645 8.092307 52.091976 8.360773 c 1303 | 52.309303 8.633501 52.603336 8.865746 52.974075 9.057507 c 1304 | 53.344814 9.253530 53.817829 9.351541 54.393112 9.351541 c 1305 | 55.151634 9.351541 55.835583 9.157649 56.444958 8.769865 c 1306 | 57.058594 8.386342 57.544392 7.817450 57.902344 7.063189 c 1307 | 58.264561 6.313189 58.445667 5.392734 58.445667 4.301825 c 1308 | 58.445667 3.223700 58.268822 2.307507 57.915127 1.553245 c 1309 | 57.561436 0.798983 57.079903 0.223700 56.470528 -0.172607 c 1310 | 55.861153 -0.568914 55.170811 -0.767067 54.399502 -0.767067 c 1311 | 53.837002 -0.767067 53.370384 -0.673317 52.999645 -0.485817 c 1312 | 52.628906 -0.298317 52.330612 -0.072464 52.104759 0.191740 c 1313 | 51.883167 0.460205 51.710583 0.713757 51.587002 0.952393 c 1314 | 51.491123 0.952393 l 1315 | 51.491123 -4.276300 l 1316 | 49.177200 -4.276300 l 1317 | h 1318 | 51.446377 4.314609 m 1319 | 51.446377 3.679666 51.535866 3.123558 51.714844 2.646285 c 1320 | 51.898083 2.169012 52.160156 1.796143 52.501064 1.527677 c 1321 | 52.846237 1.263474 53.263851 1.131371 53.753906 1.131371 c 1322 | 54.265270 1.131371 54.693535 1.267735 55.038708 1.540461 c 1323 | 55.383881 1.817450 55.643822 2.194580 55.818539 2.671853 c 1324 | 55.997513 3.153387 56.087002 3.700973 56.087002 4.314609 c 1325 | 56.087002 4.923984 55.999645 5.465177 55.824928 5.938189 c 1326 | 55.650211 6.411200 55.390270 6.781939 55.045101 7.050405 c 1327 | 54.699928 7.318871 54.269531 7.453103 53.753906 7.453103 c 1328 | 53.259586 7.453103 52.839844 7.323132 52.494675 7.063189 c 1329 | 52.149502 6.803245 51.887428 6.438898 51.708450 5.970148 c 1330 | 51.533733 5.501398 51.446377 4.949552 51.446377 4.314609 c 1331 | h 1332 | 59.238281 -0.594482 m 1333 | h 1334 | 65.329903 9.223700 m 1335 | 65.329903 7.433928 l 1336 | 59.685726 7.433928 l 1337 | 59.685726 9.223700 l 1338 | 65.329903 9.223700 l 1339 | h 1340 | 61.079189 11.575973 m 1341 | 63.393112 11.575973 l 1342 | 63.393112 2.358643 l 1343 | 63.393112 2.047563 63.439987 1.808927 63.533737 1.642734 c 1344 | 63.631748 1.480802 63.759586 1.370007 63.917259 1.310347 c 1345 | 64.074928 1.250687 64.249641 1.220858 64.441406 1.220858 c 1346 | 64.586296 1.220858 64.718399 1.231512 64.837715 1.252819 c 1347 | 64.961288 1.274126 65.055038 1.293301 65.118965 1.310347 c 1348 | 65.508881 -0.498602 l 1349 | 65.385300 -0.541216 65.208450 -0.588091 64.978340 -0.639227 c 1350 | 64.752487 -0.690363 64.475494 -0.720192 64.147369 -0.728716 c 1351 | 63.567825 -0.745762 63.045811 -0.658403 62.581322 -0.466642 c 1352 | 62.116833 -0.270618 61.748222 0.031939 61.475498 0.441029 c 1353 | 61.207031 0.850119 61.074928 1.361483 61.079189 1.975120 c 1354 | 61.079189 11.575973 l 1355 | h 1356 | 70.541016 -0.594482 m 1357 | h 1358 | 76.702950 9.223700 m 1359 | 76.702950 7.433928 l 1360 | 70.898972 7.433928 l 1361 | 70.898972 9.223700 l 1362 | 76.702950 9.223700 l 1363 | h 1364 | 72.349968 -0.594482 m 1365 | 72.349968 10.150547 l 1366 | 72.349968 10.811058 72.486328 11.360774 72.759056 11.799694 c 1367 | 73.036041 12.238615 73.406784 12.566740 73.871269 12.784069 c 1368 | 74.335754 13.001399 74.851379 13.110064 75.418144 13.110064 c 1369 | 75.818710 13.110064 76.174538 13.078104 76.485619 13.014183 c 1370 | 76.796700 12.950262 77.026810 12.892735 77.175957 12.841598 c 1371 | 76.715729 11.051825 l 1372 | 76.617722 11.081654 76.494141 11.111485 76.344994 11.141314 c 1373 | 76.195847 11.175405 76.029648 11.192450 75.846413 11.192450 c 1374 | 75.416016 11.192450 75.111328 11.088046 74.932350 10.879240 c 1375 | 74.757637 10.674694 74.670280 10.380660 74.670280 9.997137 c 1376 | 74.670280 -0.594482 l 1377 | 72.349968 -0.594482 l 1378 | h 1379 | 76.957031 -0.594482 m 1380 | h 1381 | 82.428619 -0.786243 m 1382 | 81.469810 -0.786243 80.638847 -0.575306 79.935722 -0.153431 c 1383 | 79.232597 0.268444 78.687141 0.858643 78.299362 1.617166 c 1384 | 77.915840 2.375689 77.724075 3.262052 77.724075 4.276257 c 1385 | 77.724075 5.290462 77.915840 6.178956 78.299362 6.941740 c 1386 | 78.687141 7.704524 79.232597 8.296853 79.935722 8.718728 c 1387 | 80.638847 9.140603 81.469810 9.351541 82.428619 9.351541 c 1388 | 83.387428 9.351541 84.218391 9.140603 84.921516 8.718728 c 1389 | 85.624649 8.296853 86.167969 7.704524 86.551491 6.941740 c 1390 | 86.939278 6.178956 87.133171 5.290462 87.133171 4.276257 c 1391 | 87.133171 3.262052 86.939278 2.375689 86.551491 1.617166 c 1392 | 86.167969 0.858643 85.624649 0.268444 84.921516 -0.153431 c 1393 | 84.218391 -0.575306 83.387428 -0.786243 82.428619 -0.786243 c 1394 | h 1395 | 82.441406 1.067450 m 1396 | 82.961296 1.067450 83.395950 1.210205 83.745384 1.495716 c 1397 | 84.094818 1.785489 84.354759 2.173274 84.525215 2.659069 c 1398 | 84.699928 3.144864 84.787285 3.686057 84.787285 4.282648 c 1399 | 84.787285 4.883501 84.699928 5.426825 84.525215 5.912620 c 1400 | 84.354759 6.402677 84.094818 6.792592 83.745384 7.082365 c 1401 | 83.395950 7.372137 82.961296 7.517023 82.441406 7.517023 c 1402 | 81.908737 7.517023 81.465553 7.372137 81.111862 7.082365 c 1403 | 80.762428 6.792592 80.500359 6.402677 80.325638 5.912620 c 1404 | 80.155182 5.426825 80.069954 4.883501 80.069954 4.282648 c 1405 | 80.069954 3.686057 80.155182 3.144864 80.325638 2.659069 c 1406 | 80.500359 2.173274 80.762428 1.785489 81.111862 1.495716 c 1407 | 81.465553 1.210205 81.908737 1.067450 82.441406 1.067450 c 1408 | h 1409 | 87.908203 -0.594482 m 1410 | h 1411 | 89.097122 -0.594482 m 1412 | 89.097122 9.223700 l 1413 | 91.340729 9.223700 l 1414 | 91.340729 7.587336 l 1415 | 91.443001 7.587336 l 1416 | 91.621979 8.154098 91.928802 8.590887 92.363457 8.897705 c 1417 | 92.802383 9.208785 93.303093 9.364325 93.865593 9.364325 c 1418 | 93.993431 9.364325 94.136185 9.357933 94.293854 9.345149 c 1419 | 94.455788 9.336626 94.590019 9.321712 94.696556 9.300405 c 1420 | 94.696556 7.171853 l 1421 | 94.598541 7.205944 94.443001 7.235774 94.229935 7.261342 c 1422 | 94.021126 7.291171 93.818710 7.306086 93.622688 7.306086 c 1423 | 93.200813 7.306086 92.821556 7.214466 92.484909 7.031228 c 1424 | 92.152519 6.852251 91.890450 6.602961 91.698685 6.283359 c 1425 | 91.506920 5.963757 91.411041 5.595148 91.411041 5.177535 c 1426 | 91.411041 -0.594482 l 1427 | 89.097122 -0.594482 l 1428 | h 1429 | 99.509766 -0.594482 m 1430 | h 1431 | 99.906075 12.496428 m 1432 | 102.584343 12.496428 l 1433 | 105.786751 6.705234 l 1434 | 105.914597 6.705234 l 1435 | 109.117012 12.496428 l 1436 | 111.795280 12.496428 l 1437 | 107.033203 4.289041 l 1438 | 107.033203 -0.594482 l 1439 | 104.668144 -0.594482 l 1440 | 104.668144 4.289041 l 1441 | 99.906075 12.496428 l 1442 | h 1443 | 110.355469 -0.594482 m 1444 | h 1445 | 115.827057 -0.786243 m 1446 | 114.868248 -0.786243 114.037285 -0.575306 113.334160 -0.153431 c 1447 | 112.631035 0.268444 112.085579 0.858643 111.697800 1.617166 c 1448 | 111.314278 2.375689 111.122513 3.262052 111.122513 4.276257 c 1449 | 111.122513 5.290462 111.314278 6.178956 111.697800 6.941740 c 1450 | 112.085579 7.704524 112.631035 8.296853 113.334160 8.718728 c 1451 | 114.037285 9.140603 114.868248 9.351541 115.827057 9.351541 c 1452 | 116.785866 9.351541 117.616829 9.140603 118.319954 8.718728 c 1453 | 119.023087 8.296853 119.566406 7.704524 119.949928 6.941740 c 1454 | 120.337715 6.178956 120.531609 5.290462 120.531609 4.276257 c 1455 | 120.531609 3.262052 120.337715 2.375689 119.949928 1.617166 c 1456 | 119.566406 0.858643 119.023087 0.268444 118.319954 -0.153431 c 1457 | 117.616829 -0.575306 116.785866 -0.786243 115.827057 -0.786243 c 1458 | h 1459 | 115.839844 1.067450 m 1460 | 116.359734 1.067450 116.794388 1.210205 117.143822 1.495716 c 1461 | 117.493256 1.785489 117.753197 2.173274 117.923653 2.659069 c 1462 | 118.098366 3.144864 118.185722 3.686057 118.185722 4.282648 c 1463 | 118.185722 4.883501 118.098366 5.426825 117.923653 5.912620 c 1464 | 117.753197 6.402677 117.493256 6.792592 117.143822 7.082365 c 1465 | 116.794388 7.372137 116.359734 7.517023 115.839844 7.517023 c 1466 | 115.307175 7.517023 114.863991 7.372137 114.510300 7.082365 c 1467 | 114.160866 6.792592 113.898796 6.402677 113.724075 5.912620 c 1468 | 113.553619 5.426825 113.468391 4.883501 113.468391 4.282648 c 1469 | 113.468391 3.686057 113.553619 3.144864 113.724075 2.659069 c 1470 | 113.898796 2.173274 114.160866 1.785489 114.510300 1.495716 c 1471 | 114.863991 1.210205 115.307175 1.067450 115.839844 1.067450 c 1472 | h 1473 | 121.306641 -0.594482 m 1474 | h 1475 | 128.753372 3.534780 m 1476 | 128.753372 9.223700 l 1477 | 131.067291 9.223700 l 1478 | 131.067291 -0.594482 l 1479 | 128.823685 -0.594482 l 1480 | 128.823685 1.150545 l 1481 | 128.721420 1.150545 l 1482 | 128.499817 0.600830 128.135468 0.151257 127.628372 -0.198175 c 1483 | 127.125534 -0.547607 126.505508 -0.722322 125.768288 -0.722322 c 1484 | 125.124825 -0.722322 124.555931 -0.579567 124.061615 -0.294056 c 1485 | 123.571556 -0.004284 123.188034 0.415461 122.911041 0.965178 c 1486 | 122.634056 1.519155 122.495560 2.188189 122.495560 2.972280 c 1487 | 122.495560 9.223700 l 1488 | 124.809479 9.223700 l 1489 | 124.809479 3.330234 l 1490 | 124.809479 2.708075 124.979935 2.213756 125.320847 1.847279 c 1491 | 125.661751 1.480802 126.109200 1.297564 126.663177 1.297564 c 1492 | 127.004082 1.297564 127.334335 1.380661 127.653938 1.546853 c 1493 | 127.973541 1.713046 128.235626 1.960205 128.440170 2.288330 c 1494 | 128.648972 2.620717 128.753372 3.036200 128.753372 3.534780 c 1495 | h 1496 | 132.257812 -0.594482 m 1497 | h 1498 | 133.446732 -0.594482 m 1499 | 133.446732 9.223700 l 1500 | 135.690338 9.223700 l 1501 | 135.690338 7.587336 l 1502 | 135.792618 7.587336 l 1503 | 135.971588 8.154098 136.278412 8.590887 136.713074 8.897705 c 1504 | 137.151993 9.208785 137.652695 9.364325 138.215195 9.364325 c 1505 | 138.343048 9.364325 138.485794 9.357933 138.643463 9.345149 c 1506 | 138.805405 9.336626 138.939636 9.321712 139.046158 9.300405 c 1507 | 139.046158 7.171853 l 1508 | 138.948151 7.205944 138.792618 7.235774 138.579544 7.261342 c 1509 | 138.370743 7.291171 138.168335 7.306086 137.972305 7.306086 c 1510 | 137.550430 7.306086 137.171173 7.214466 136.834518 7.031228 c 1511 | 136.502136 6.852251 136.240051 6.602961 136.048294 6.283359 c 1512 | 135.856537 5.963757 135.760651 5.595148 135.760651 5.177535 c 1513 | 135.760651 -0.594482 l 1514 | 133.446732 -0.594482 l 1515 | h 1516 | 143.859375 -0.594482 m 1517 | h 1518 | 145.150574 -0.594482 m 1519 | 145.150574 12.496428 l 1520 | 150.059662 12.496428 l 1521 | 151.065338 12.496428 151.909088 12.308928 152.590912 11.933928 c 1522 | 153.276993 11.558928 153.794739 11.043303 154.144180 10.387053 c 1523 | 154.497864 9.735064 154.674713 8.993586 154.674713 8.162620 c 1524 | 154.674713 7.323132 154.497864 6.577393 154.144180 5.925404 c 1525 | 153.790482 5.273416 153.268463 4.759921 152.578125 4.384921 c 1526 | 151.887787 4.014183 151.037643 3.828814 150.027695 3.828814 c 1527 | 146.774155 3.828814 l 1528 | 146.774155 5.778387 l 1529 | 149.708099 5.778387 l 1530 | 150.296158 5.778387 150.777695 5.880660 151.152695 6.085205 c 1531 | 151.527695 6.289750 151.804688 6.571000 151.983658 6.928955 c 1532 | 152.166901 7.286910 152.258530 7.698132 152.258530 8.162620 c 1533 | 152.258530 8.627109 152.166901 9.036200 151.983658 9.389893 c 1534 | 151.804688 9.743586 151.525558 10.018444 151.146301 10.214467 c 1535 | 150.771301 10.414751 150.287643 10.514893 149.695312 10.514893 c 1536 | 147.522018 10.514893 l 1537 | 147.522018 -0.594482 l 1538 | 145.150574 -0.594482 l 1539 | h 1540 | 155.267578 -0.594482 m 1541 | h 1542 | 159.281784 -0.792635 m 1543 | 158.659622 -0.792635 158.099258 -0.681839 157.600677 -0.460249 c 1544 | 157.106354 -0.234398 156.714310 0.097988 156.424545 0.536909 c 1545 | 156.139038 0.975830 155.996277 1.517023 155.996277 2.160489 c 1546 | 155.996277 2.714466 156.098541 3.172564 156.303085 3.534780 c 1547 | 156.507629 3.896996 156.786758 4.186768 157.140442 4.404098 c 1548 | 157.494141 4.621427 157.892578 4.785489 158.335754 4.896285 c 1549 | 158.783203 5.011341 159.245560 5.094438 159.722839 5.145575 c 1550 | 160.298111 5.205234 160.764740 5.258501 161.122696 5.305376 c 1551 | 161.480652 5.356512 161.740585 5.433217 161.902527 5.535490 c 1552 | 162.068710 5.642024 162.151810 5.806087 162.151810 6.027678 c 1553 | 162.151810 6.066029 l 1554 | 162.151810 6.547563 162.009048 6.920432 161.723541 7.184637 c 1555 | 161.438034 7.448842 161.026810 7.580944 160.489883 7.580944 c 1556 | 159.923126 7.580944 159.473541 7.457364 159.141159 7.210205 c 1557 | 158.813034 6.963046 158.591446 6.671143 158.476379 6.334495 c 1558 | 156.315872 6.641314 l 1559 | 156.486328 7.237905 156.767578 7.736484 157.159622 8.137053 c 1560 | 157.551666 8.541883 158.031082 8.844439 158.597839 9.044723 c 1561 | 159.164597 9.249268 159.791016 9.351541 160.477097 9.351541 c 1562 | 160.950104 9.351541 161.420990 9.296143 161.889740 9.185348 c 1563 | 162.358490 9.074553 162.786758 8.891314 163.174545 8.635632 c 1564 | 163.562317 8.384212 163.873398 8.041171 164.107773 7.606512 c 1565 | 164.346405 7.171853 164.465729 6.628529 164.465729 5.976541 c 1566 | 164.465729 -0.594482 l 1567 | 162.241302 -0.594482 l 1568 | 162.241302 0.754240 l 1569 | 162.164597 0.754240 l 1570 | 162.023972 0.481512 161.825821 0.225830 161.570129 -0.012806 c 1571 | 161.318710 -0.247181 161.001251 -0.436811 160.617722 -0.581697 c 1572 | 160.238464 -0.722322 159.793152 -0.792635 159.281784 -0.792635 c 1573 | h 1574 | 159.882629 0.907648 m 1575 | 160.347122 0.907648 160.749817 0.999268 161.090729 1.182507 c 1576 | 161.431641 1.370007 161.693710 1.617166 161.876953 1.923984 c 1577 | 162.064453 2.230802 162.158203 2.565319 162.158203 2.927535 c 1578 | 162.158203 4.084495 l 1579 | 162.085770 4.024836 161.962189 3.969439 161.787460 3.918303 c 1580 | 161.617004 3.867166 161.425247 3.822421 161.212173 3.784069 c 1581 | 160.999115 3.745717 160.788177 3.711626 160.579361 3.681796 c 1582 | 160.370560 3.651966 160.189453 3.626398 160.036041 3.605092 c 1583 | 159.690872 3.558217 159.381927 3.481512 159.109192 3.374978 c 1584 | 158.836472 3.268444 158.621277 3.119296 158.463608 2.927535 c 1585 | 158.305939 2.740035 158.227097 2.497137 158.227097 2.198841 c 1586 | 158.227097 1.772705 158.382629 1.450973 158.693710 1.233643 c 1587 | 159.004791 1.016314 159.401108 0.907648 159.882629 0.907648 c 1588 | h 1589 | 165.251953 -0.594482 m 1590 | h 1591 | 167.879089 -4.276300 m 1592 | 167.563736 -4.276300 167.271835 -4.250732 167.003372 -4.199596 c 1593 | 166.739166 -4.152721 166.528229 -4.097324 166.370560 -4.033403 c 1594 | 166.907486 -2.230846 l 1595 | 167.244141 -2.328857 167.544571 -2.375732 167.808777 -2.371471 c 1596 | 168.072983 -2.367210 168.305222 -2.284113 168.505508 -2.122181 c 1597 | 168.710052 -1.964510 168.882629 -1.700306 169.023254 -1.329567 c 1598 | 169.221420 -0.799028 l 1599 | 165.661041 9.223700 l 1600 | 168.115585 9.223700 l 1601 | 170.378372 1.808927 l 1602 | 170.480652 1.808927 l 1603 | 172.749817 9.223700 l 1604 | 175.210754 9.223700 l 1605 | 171.279648 -1.783403 l 1606 | 171.096405 -2.303289 170.853516 -2.748602 170.550964 -3.119341 c 1607 | 170.248398 -3.494341 169.877655 -3.779852 169.438736 -3.975874 c 1608 | 169.004089 -4.176159 168.484207 -4.276300 167.879089 -4.276300 c 1609 | h 1610 | 175.623047 -0.594482 m 1611 | h 1612 | 176.811966 -0.594482 m 1613 | 176.811966 9.223700 l 1614 | 179.023621 9.223700 l 1615 | 179.023621 7.555376 l 1616 | 179.138672 7.555376 l 1617 | 179.343216 8.117876 179.682007 8.556796 180.155014 8.872137 c 1618 | 180.628021 9.191739 181.192642 9.351541 181.848892 9.351541 c 1619 | 182.513672 9.351541 183.074036 9.189609 183.530014 8.865745 c 1620 | 183.990234 8.546143 184.314102 8.109353 184.501602 7.555376 c 1621 | 184.603867 7.555376 l 1622 | 184.821198 8.100830 185.187683 8.535489 185.703308 8.859353 c 1623 | 186.223190 9.187478 186.838959 9.351541 187.550598 9.351541 c 1624 | 188.454010 9.351541 189.191223 9.066030 189.762253 8.495007 c 1625 | 190.333282 7.923984 190.618790 7.090887 190.618790 5.995716 c 1626 | 190.618790 -0.594482 l 1627 | 188.298477 -0.594482 l 1628 | 188.298477 5.637762 l 1629 | 188.298477 6.247137 188.136551 6.692450 187.812683 6.973700 c 1630 | 187.488815 7.259212 187.092514 7.401967 186.623764 7.401967 c 1631 | 186.065521 7.401967 185.628723 7.227251 185.313385 6.877819 c 1632 | 185.002304 6.532649 184.846771 6.083075 184.846771 5.529098 c 1633 | 184.846771 -0.594482 l 1634 | 182.577591 -0.594482 l 1635 | 182.577591 5.733643 l 1636 | 182.577591 6.240745 182.424179 6.645575 182.117371 6.948132 c 1637 | 181.814804 7.250689 181.418503 7.401967 180.928452 7.401967 c 1638 | 180.596069 7.401967 180.293503 7.316740 180.020767 7.146285 c 1639 | 179.748047 6.980091 179.530716 6.743586 179.368790 6.436768 c 1640 | 179.206848 6.134211 179.125885 5.780518 179.125885 5.375689 c 1641 | 179.125885 -0.594482 l 1642 | 176.811966 -0.594482 l 1643 | h 1644 | 191.777344 -0.594482 m 1645 | h 1646 | 197.312851 -0.786243 m 1647 | 196.328476 -0.786243 195.478333 -0.581697 194.762436 -0.172607 c 1648 | 194.050781 0.240744 193.503204 0.824551 193.119675 1.578814 c 1649 | 192.736145 2.337337 192.544388 3.230092 192.544388 4.257080 c 1650 | 192.544388 5.267023 192.736145 6.153387 193.119675 6.916171 c 1651 | 193.507462 7.683217 194.048660 8.279808 194.743256 8.705944 c 1652 | 195.437851 9.136342 196.253906 9.351541 197.191406 9.351541 c 1653 | 197.796524 9.351541 198.367538 9.253530 198.904480 9.057507 c 1654 | 199.445663 8.865746 199.922943 8.567450 200.336288 8.162620 c 1655 | 200.753906 7.757791 201.082031 7.242166 201.320663 6.615745 c 1656 | 201.559296 5.993586 201.678619 5.252109 201.678619 4.391314 c 1657 | 201.678619 3.681796 l 1658 | 193.631042 3.681796 l 1659 | 193.631042 5.241455 l 1660 | 199.460587 5.241455 l 1661 | 199.456329 5.684637 199.360443 6.078814 199.172943 6.423984 c 1662 | 198.985443 6.773416 198.723373 7.048273 198.386719 7.248557 c 1663 | 198.054337 7.448841 197.666550 7.548984 197.223373 7.548984 c 1664 | 196.750366 7.548984 196.334869 7.433927 195.976913 7.203814 c 1665 | 195.618958 6.977962 195.339844 6.679666 195.139557 6.308928 c 1666 | 194.943527 5.942450 194.843384 5.539750 194.839127 5.100830 c 1667 | 194.839127 3.739325 l 1668 | 194.839127 3.168302 194.943527 2.678245 195.152344 2.269154 c 1669 | 195.361160 1.864325 195.653061 1.553245 196.028061 1.335915 c 1670 | 196.403061 1.122847 196.841980 1.016314 197.344818 1.016314 c 1671 | 197.681473 1.016314 197.986160 1.063189 198.258881 1.156939 c 1672 | 198.531601 1.254950 198.768112 1.397705 198.968399 1.585205 c 1673 | 199.168671 1.772705 199.319946 2.004950 199.422226 2.281939 c 1674 | 201.582748 2.039041 l 1675 | 201.446381 1.468018 201.186432 0.969439 200.802917 0.543303 c 1676 | 200.423660 0.121428 199.937851 -0.206697 199.345520 -0.441072 c 1677 | 198.753189 -0.671186 198.075638 -0.786243 197.312851 -0.786243 c 1678 | h 1679 | 202.447266 -0.594482 m 1680 | h 1681 | 205.950104 5.158359 m 1682 | 205.950104 -0.594482 l 1683 | 203.636185 -0.594482 l 1684 | 203.636185 9.223700 l 1685 | 205.847839 9.223700 l 1686 | 205.847839 7.555376 l 1687 | 205.962891 7.555376 l 1688 | 206.188736 8.105092 206.548828 8.541882 207.043152 8.865745 c 1689 | 207.541733 9.189609 208.157486 9.351541 208.890442 9.351541 c 1690 | 209.568008 9.351541 210.158203 9.206655 210.661041 8.916882 c 1691 | 211.168137 8.627109 211.560181 8.207364 211.837173 7.657648 c 1692 | 212.118423 7.107932 212.256927 6.441030 212.252670 5.656939 c 1693 | 212.252670 -0.594482 l 1694 | 209.938736 -0.594482 l 1695 | 209.938736 5.298984 l 1696 | 209.938736 5.955234 209.768280 6.468728 209.427383 6.839467 c 1697 | 209.090729 7.210205 208.624115 7.395575 208.027527 7.395575 c 1698 | 207.622696 7.395575 207.262604 7.306086 206.947266 7.127109 c 1699 | 206.636185 6.952393 206.391159 6.698842 206.212173 6.366455 c 1700 | 206.037460 6.034069 205.950104 5.631371 205.950104 5.158359 c 1701 | h 1702 | 213.416016 -0.594482 m 1703 | h 1704 | 219.507629 9.223700 m 1705 | 219.507629 7.433928 l 1706 | 213.863464 7.433928 l 1707 | 213.863464 9.223700 l 1708 | 219.507629 9.223700 l 1709 | h 1710 | 215.256927 11.575973 m 1711 | 217.570847 11.575973 l 1712 | 217.570847 2.358643 l 1713 | 217.570847 2.047563 217.617722 1.808927 217.711472 1.642734 c 1714 | 217.809479 1.480802 217.937317 1.370007 218.094986 1.310347 c 1715 | 218.252655 1.250687 218.427383 1.220858 218.619141 1.220858 c 1716 | 218.764023 1.220858 218.896133 1.231512 219.015442 1.252819 c 1717 | 219.139023 1.274126 219.232773 1.293301 219.296692 1.310347 c 1718 | 219.686615 -0.498602 l 1719 | 219.563034 -0.541216 219.386185 -0.588091 219.156067 -0.639227 c 1720 | 218.930222 -0.690363 218.653229 -0.720192 218.325104 -0.728716 c 1721 | 217.745560 -0.745762 217.223541 -0.658403 216.759048 -0.466642 c 1722 | 216.294556 -0.270618 215.925949 0.031939 215.653229 0.441029 c 1723 | 215.384766 0.850119 215.252670 1.361483 215.256927 1.975120 c 1724 | 215.256927 11.575973 l 1725 | h 1726 | f 1727 | n 1728 | Q 1729 | q 1730 | 1.000000 0.000000 -0.000000 1.000000 54.000000 543.594482 cm 1731 | BT 1732 | 18.000000 0.000000 0.000000 18.000000 0.000000 -0.594482 Tm 1733 | /F1 1.000000 Tf 1734 | [ (\020) 7.990042 (\016) (\017) (\016) (\014) (\015) (\010) (\t) (\013) 20.685408 (\007) (\n) (\t) (\006) 102.006276 (\007) (\005) (\n) (\t) (\004) 9.676615 (\003) 19.886441 (\002) (\001) (\016) (\000) (\010) ] TJ 1735 | ET 1736 | Q 1737 | q 1738 | 1.000000 0.000000 -0.000000 1.000000 54.000000 513.477234 cm 1739 | 0.683333 0.683333 0.683333 scn 1740 | 0.000000 -0.477234 m 1741 | h 1742 | 0.676136 8.610834 m 1743 | 0.676136 9.704584 l 1744 | 8.312500 9.704584 l 1745 | 8.312500 8.610834 l 1746 | 5.110796 8.610834 l 1747 | 5.110796 -0.477234 l 1748 | 3.877841 -0.477234 l 1749 | 3.877841 8.610834 l 1750 | 0.676136 8.610834 l 1751 | h 1752 | 8.982422 -0.477234 m 1753 | h 1754 | 11.229581 4.116516 m 1755 | 11.229581 -0.477234 l 1756 | 10.056286 -0.477234 l 1757 | 10.056286 9.704584 l 1758 | 11.229581 9.704584 l 1759 | 11.229581 5.965948 l 1760 | 11.329013 5.965948 l 1761 | 11.507990 6.360361 11.776456 6.673571 12.134411 6.905579 c 1762 | 12.495680 7.140901 12.976267 7.258562 13.576172 7.258562 c 1763 | 14.096532 7.258562 14.552260 7.154158 14.943359 6.945352 c 1764 | 15.334458 6.739859 15.637725 6.423334 15.853161 5.995778 c 1765 | 16.071911 5.571535 16.181286 5.031289 16.181286 4.375039 c 1766 | 16.181286 -0.477234 l 1767 | 15.007990 -0.477234 l 1768 | 15.007990 4.295493 l 1769 | 15.007990 4.902027 14.850556 5.371015 14.535689 5.702454 c 1770 | 14.224136 6.037208 13.791608 6.204585 13.238104 6.204585 c 1771 | 12.853634 6.204585 12.508937 6.123382 12.204013 5.960977 c 1772 | 11.902403 5.798571 11.663767 5.561592 11.488104 5.250039 c 1773 | 11.315755 4.938486 11.229581 4.560645 11.229581 4.116516 c 1774 | h 1775 | 17.253906 -0.477234 m 1776 | h 1777 | 20.574928 -0.656211 m 1778 | 20.091028 -0.656211 19.651871 -0.565065 19.257458 -0.382773 c 1779 | 18.863045 -0.197167 18.549835 0.069641 18.317827 0.417652 c 1780 | 18.085819 0.768978 17.969816 1.193221 17.969816 1.690380 c 1781 | 17.969816 2.127880 18.055990 2.482520 18.228338 2.754300 c 1782 | 18.400686 3.029395 18.631037 3.244831 18.919390 3.400607 c 1783 | 19.207741 3.556383 19.525923 3.672387 19.873934 3.748618 c 1784 | 20.225260 3.828163 20.578243 3.891137 20.932884 3.937539 c 1785 | 21.396898 3.997198 21.773083 4.041943 22.061436 4.071772 c 1786 | 22.353102 4.104916 22.565224 4.159603 22.697798 4.235834 c 1787 | 22.833689 4.312065 22.901634 4.444641 22.901634 4.633562 c 1788 | 22.901634 4.673334 l 1789 | 22.901634 5.163865 22.767401 5.545020 22.498936 5.816800 c 1790 | 22.233784 6.088581 21.831085 6.224471 21.290838 6.224471 c 1791 | 20.730705 6.224471 20.291548 6.101838 19.973366 5.856573 c 1792 | 19.655184 5.611308 19.431463 5.349471 19.302202 5.071062 c 1793 | 18.188566 5.468789 l 1794 | 18.387428 5.932805 18.652580 6.294073 18.984020 6.552596 c 1795 | 19.318773 6.814433 19.683357 6.996725 20.077770 7.099471 c 1796 | 20.475496 7.205531 20.866596 7.258562 21.251066 7.258562 c 1797 | 21.496330 7.258562 21.778053 7.228732 22.096235 7.169074 c 1798 | 22.417732 7.112729 22.727627 6.995068 23.025923 6.816091 c 1799 | 23.327534 6.637113 23.577770 6.366990 23.776634 6.005721 c 1800 | 23.975498 5.644452 24.074930 5.160550 24.074930 4.554016 c 1801 | 24.074930 -0.477234 l 1802 | 22.901634 -0.477234 l 1803 | 22.901634 0.556857 l 1804 | 22.841974 0.556857 l 1805 | 22.762428 0.391137 22.629854 0.213817 22.444248 0.024897 c 1806 | 22.258642 -0.164023 22.011719 -0.324772 21.703480 -0.457348 c 1807 | 21.395243 -0.589923 21.019058 -0.656211 20.574928 -0.656211 c 1808 | h 1809 | 20.753906 0.397766 m 1810 | 21.217920 0.397766 21.609020 0.488912 21.927202 0.671204 c 1811 | 22.248699 0.853496 22.490648 1.088818 22.653053 1.377170 c 1812 | 22.818773 1.665522 22.901634 1.968789 22.901634 2.286971 c 1813 | 22.901634 3.360834 l 1814 | 22.851919 3.301175 22.742544 3.246488 22.573509 3.196772 c 1815 | 22.407789 3.150371 22.215553 3.108940 21.996803 3.072482 c 1816 | 21.781368 3.039338 21.570904 3.009508 21.365412 2.982993 c 1817 | 21.163235 2.959793 20.999172 2.939906 20.873224 2.923334 c 1818 | 20.568300 2.883561 20.283262 2.818931 20.018110 2.729443 c 1819 | 19.756273 2.643268 19.544151 2.512349 19.381746 2.336687 c 1820 | 19.222656 2.164338 19.143110 1.929016 19.143110 1.630721 c 1821 | 19.143110 1.223050 19.293915 0.914811 19.595526 0.706005 c 1822 | 19.900450 0.500512 20.286577 0.397766 20.753906 0.397766 c 1823 | h 1824 | 25.142578 -0.477234 m 1825 | h 1826 | 27.389738 4.116516 m 1827 | 27.389738 -0.477234 l 1828 | 26.216442 -0.477234 l 1829 | 26.216442 7.159130 l 1830 | 27.349964 7.159130 l 1831 | 27.349964 5.965948 l 1832 | 27.449396 5.965948 l 1833 | 27.628374 6.353732 27.900154 6.665285 28.264738 6.900607 c 1834 | 28.629322 7.139244 29.099966 7.258562 29.676670 7.258562 c 1835 | 30.193716 7.258562 30.646130 7.152501 31.033915 6.940380 c 1836 | 31.421698 6.731573 31.723307 6.413391 31.938744 5.985835 c 1837 | 32.154179 5.561592 32.261898 5.024660 32.261898 4.375039 c 1838 | 32.261898 -0.477234 l 1839 | 31.088602 -0.477234 l 1840 | 31.088602 4.295493 l 1841 | 31.088602 4.895399 30.932825 5.362728 30.621271 5.697482 c 1842 | 30.309719 6.035551 29.882162 6.204585 29.338600 6.204585 c 1843 | 28.964075 6.204585 28.629320 6.123382 28.334339 5.960977 c 1844 | 28.042673 5.798571 27.812323 5.561592 27.643288 5.250039 c 1845 | 27.474255 4.938486 27.389738 4.560645 27.389738 4.116516 c 1846 | h 1847 | 33.332031 -0.477234 m 1848 | h 1849 | 35.499645 2.306857 m 1850 | 35.479759 3.758561 l 1851 | 35.718395 3.758561 l 1852 | 39.059303 7.159130 l 1853 | 40.511009 7.159130 l 1854 | 36.951351 3.559698 l 1855 | 36.851917 3.559698 l 1856 | 35.499645 2.306857 l 1857 | h 1858 | 34.405895 -0.477234 m 1859 | 34.405895 9.704584 l 1860 | 35.579189 9.704584 l 1861 | 35.579189 -0.477234 l 1862 | 34.405895 -0.477234 l 1863 | h 1864 | 39.258167 -0.477234 m 1865 | 36.275211 3.301175 l 1866 | 37.110439 4.116516 l 1867 | 40.749645 -0.477234 l 1868 | 39.258167 -0.477234 l 1869 | h 1870 | 40.947266 -0.477234 m 1871 | h 1872 | 47.430222 5.448903 m 1873 | 46.376244 5.150607 l 1874 | 46.309956 5.326270 46.212181 5.496962 46.082920 5.662681 c 1875 | 45.956970 5.831715 45.784622 5.970920 45.565872 6.080295 c 1876 | 45.347122 6.189670 45.067055 6.244357 44.725674 6.244357 c 1877 | 44.258347 6.244357 43.868904 6.136639 43.557350 5.921204 c 1878 | 43.249111 5.709083 43.094994 5.438959 43.094994 5.110834 c 1879 | 43.094994 4.819167 43.201054 4.588818 43.413174 4.419784 c 1880 | 43.625298 4.250750 43.956738 4.109887 44.407494 3.997198 c 1881 | 45.541016 3.718789 l 1882 | 46.223782 3.553069 46.732540 3.299518 47.067295 2.958136 c 1883 | 47.402046 2.620068 47.569424 2.184225 47.569424 1.650607 c 1884 | 47.569424 1.213107 47.443478 0.822008 47.191586 0.477311 c 1885 | 46.943005 0.132614 46.594994 -0.139166 46.147549 -0.338029 c 1886 | 45.700104 -0.536893 45.179745 -0.636325 44.586472 -0.636325 c 1887 | 43.807590 -0.636325 43.162941 -0.467291 42.652523 -0.129223 c 1888 | 42.142105 0.208846 41.818951 0.702691 41.683060 1.352312 c 1889 | 42.796696 1.630721 l 1890 | 42.902756 1.219736 43.103279 0.911497 43.398258 0.706005 c 1891 | 43.696556 0.500512 44.085999 0.397766 44.566586 0.397766 c 1892 | 45.113461 0.397766 45.547646 0.513770 45.869141 0.745777 c 1893 | 46.193951 0.981099 46.356358 1.262823 46.356358 1.590948 c 1894 | 46.356358 1.856099 46.263554 2.078164 46.077946 2.257141 c 1895 | 45.892342 2.439433 45.607304 2.575323 45.222836 2.664812 c 1896 | 43.950108 2.963107 l 1897 | 43.250771 3.128826 42.737038 3.385692 42.408913 3.733704 c 1898 | 42.084103 4.085030 41.921696 4.524187 41.921696 5.051175 c 1899 | 41.921696 5.482046 42.042671 5.863202 42.284622 6.194641 c 1900 | 42.529888 6.526081 42.862988 6.786261 43.283913 6.975182 c 1901 | 43.708157 7.164102 44.188744 7.258562 44.725674 7.258562 c 1902 | 45.481354 7.258562 46.074631 7.092842 46.505505 6.761403 c 1903 | 46.939690 6.429964 47.247929 5.992464 47.430222 5.448903 c 1904 | h 1905 | 52.199219 -0.477234 m 1906 | h 1907 | 56.673649 7.159130 m 1908 | 56.673649 6.164812 l 1909 | 52.557175 6.164812 l 1910 | 52.557175 7.159130 l 1911 | 56.673649 7.159130 l 1912 | h 1913 | 53.790127 -0.477234 m 1914 | 53.790127 8.213107 l 1915 | 53.790127 8.650608 53.892872 9.015191 54.098366 9.306858 c 1916 | 54.303860 9.598524 54.570667 9.817274 54.898792 9.963108 c 1917 | 55.226917 10.108941 55.573273 10.181858 55.937855 10.181858 c 1918 | 56.226208 10.181858 56.461529 10.158657 56.643822 10.112255 c 1919 | 56.826115 10.065854 56.962002 10.022766 57.051491 9.982994 c 1920 | 56.713425 8.968789 l 1921 | 56.653767 8.988676 56.570904 9.013534 56.464844 9.043363 c 1922 | 56.362099 9.073193 56.226208 9.088108 56.057175 9.088108 c 1923 | 55.669392 9.088108 55.389324 8.990333 55.216976 8.794783 c 1924 | 55.047943 8.599234 54.963425 8.312539 54.963425 7.934699 c 1925 | 54.963425 -0.477234 l 1926 | 53.790127 -0.477234 l 1927 | h 1928 | 56.929688 -0.477234 m 1929 | h 1930 | 61.105824 -0.636325 m 1931 | 60.416428 -0.636325 59.811554 -0.472262 59.291195 -0.144137 c 1932 | 58.774147 0.183988 58.369793 0.643031 58.078125 1.232993 c 1933 | 57.789772 1.822956 57.645596 2.512350 57.645596 3.301175 c 1934 | 57.645596 4.096630 57.789772 4.790996 58.078125 5.384272 c 1935 | 58.369793 5.977549 58.774147 6.438250 59.291195 6.766374 c 1936 | 59.811554 7.094499 60.416428 7.258562 61.105824 7.258562 c 1937 | 61.795219 7.258562 62.398438 7.094499 62.915482 6.766374 c 1938 | 63.435841 6.438250 63.840199 5.977549 64.128548 5.384272 c 1939 | 64.420212 4.790996 64.566048 4.096630 64.566048 3.301175 c 1940 | 64.566048 2.512350 64.420212 1.822956 64.128548 1.232993 c 1941 | 63.840199 0.643031 63.435841 0.183988 62.915482 -0.144137 c 1942 | 62.398438 -0.472262 61.795219 -0.636325 61.105824 -0.636325 c 1943 | h 1944 | 61.105824 0.417652 m 1945 | 61.629498 0.417652 62.060368 0.551886 62.398438 0.820352 c 1946 | 62.736507 1.088818 62.986744 1.441800 63.149147 1.879300 c 1947 | 63.311554 2.316800 63.392757 2.790758 63.392757 3.301175 c 1948 | 63.392757 3.811592 63.311554 4.287208 63.149147 4.728022 c 1949 | 62.986744 5.168836 62.736507 5.525134 62.398438 5.796914 c 1950 | 62.060368 6.068695 61.629498 6.204585 61.105824 6.204585 c 1951 | 60.582150 6.204585 60.151279 6.068695 59.813210 5.796914 c 1952 | 59.475140 5.525134 59.224903 5.168836 59.062500 4.728022 c 1953 | 58.900097 4.287208 58.818893 3.811592 58.818893 3.301175 c 1954 | 58.818893 2.790758 58.900097 2.316800 59.062500 1.879300 c 1955 | 59.224903 1.441800 59.475140 1.088818 59.813210 0.820352 c 1956 | 60.151279 0.551886 60.582150 0.417652 61.105824 0.417652 c 1957 | h 1958 | 65.283203 -0.477234 m 1959 | h 1960 | 66.357063 -0.477234 m 1961 | 66.357063 7.159130 l 1962 | 67.490593 7.159130 l 1963 | 67.490593 6.005721 l 1964 | 67.570137 6.005721 l 1965 | 67.709335 6.383562 67.961227 6.690143 68.325813 6.925466 c 1966 | 68.690399 7.160788 69.101379 7.278449 69.558769 7.278449 c 1967 | 69.644943 7.278449 69.752663 7.276791 69.881927 7.273477 c 1968 | 70.011185 7.270162 70.108955 7.265191 70.175247 7.258562 c 1969 | 70.175247 6.065380 l 1970 | 70.135475 6.075323 70.044334 6.090238 69.901810 6.110125 c 1971 | 69.762604 6.133325 69.615112 6.144925 69.459343 6.144925 c 1972 | 69.088127 6.144925 68.756683 6.067037 68.465019 5.911261 c 1973 | 68.176666 5.758799 67.947975 5.546677 67.778938 5.274897 c 1974 | 67.613220 5.006431 67.530365 4.699850 67.530365 4.355153 c 1975 | 67.530365 -0.477234 l 1976 | 66.357063 -0.477234 l 1977 | h 1978 | 74.429688 -0.477234 m 1979 | h 1980 | 78.824577 7.159130 m 1981 | 78.824577 6.164812 l 1982 | 74.867188 6.164812 l 1983 | 74.867188 7.159130 l 1984 | 78.824577 7.159130 l 1985 | h 1986 | 76.020599 8.988675 m 1987 | 77.193893 8.988675 l 1988 | 77.193893 1.710266 l 1989 | 77.193893 1.378827 77.241951 1.130247 77.338066 0.964527 c 1990 | 77.437500 0.802122 77.563446 0.692747 77.715912 0.636402 c 1991 | 77.871689 0.583372 78.035751 0.556857 78.208099 0.556857 c 1992 | 78.337357 0.556857 78.443413 0.563486 78.526276 0.576743 c 1993 | 78.609138 0.593315 78.675423 0.606573 78.725143 0.616516 c 1994 | 78.963776 -0.437461 l 1995 | 78.884232 -0.467291 78.773201 -0.497120 78.630684 -0.526950 c 1996 | 78.488159 -0.560094 78.307526 -0.576666 78.088776 -0.576666 c 1997 | 77.757339 -0.576666 77.432533 -0.505406 77.114349 -0.362887 c 1998 | 76.799477 -0.220368 76.537643 -0.003276 76.328835 0.288391 c 1999 | 76.123344 0.580058 76.020599 0.947955 76.020599 1.392084 c 2000 | 76.020599 8.988675 l 2001 | h 2002 | 79.679688 -0.477234 m 2003 | h 2004 | 81.926849 4.116516 m 2005 | 81.926849 -0.477234 l 2006 | 80.753548 -0.477234 l 2007 | 80.753548 9.704584 l 2008 | 81.926849 9.704584 l 2009 | 81.926849 5.965948 l 2010 | 82.026276 5.965948 l 2011 | 82.205254 6.360361 82.473724 6.673571 82.831673 6.905579 c 2012 | 83.192947 7.140901 83.673531 7.258562 84.273438 7.258562 c 2013 | 84.793800 7.258562 85.249527 7.154158 85.640625 6.945352 c 2014 | 86.031723 6.739859 86.334991 6.423334 86.550423 5.995778 c 2015 | 86.769173 5.571535 86.878548 5.031289 86.878548 4.375039 c 2016 | 86.878548 -0.477234 l 2017 | 85.705254 -0.477234 l 2018 | 85.705254 4.295493 l 2019 | 85.705254 4.902027 85.547821 5.371015 85.232956 5.702454 c 2020 | 84.921402 6.037208 84.488876 6.204585 83.935371 6.204585 c 2021 | 83.550903 6.204585 83.206200 6.123382 82.901276 5.960977 c 2022 | 82.599663 5.798571 82.361031 5.561592 82.185371 5.250039 c 2023 | 82.013023 4.938486 81.926849 4.560645 81.926849 4.116516 c 2024 | h 2025 | 87.951172 -0.477234 m 2026 | h 2027 | 92.226738 -0.636325 m 2028 | 91.490944 -0.636325 90.856239 -0.473920 90.322624 -0.149109 c 2029 | 89.792320 0.179016 89.382988 0.636402 89.094635 1.223050 c 2030 | 88.809601 1.813012 88.667084 2.499092 88.667084 3.281289 c 2031 | 88.667084 4.063486 88.809601 4.752880 89.094635 5.349471 c 2032 | 89.382988 5.949376 89.784035 6.416706 90.297760 6.751460 c 2033 | 90.814812 7.089528 91.418030 7.258562 92.107422 7.258562 c 2034 | 92.505150 7.258562 92.897903 7.192274 93.285690 7.059699 c 2035 | 93.673470 6.927123 94.026451 6.711687 94.344635 6.413391 c 2036 | 94.662819 6.118410 94.916367 5.727312 95.105293 5.240096 c 2037 | 95.294212 4.752880 95.388672 4.152974 95.388672 3.440380 c 2038 | 95.388672 2.943221 l 2039 | 89.502312 2.943221 l 2040 | 89.502312 3.957425 l 2041 | 94.195488 3.957425 l 2042 | 94.195488 4.388296 94.109314 4.772766 93.936966 5.110834 c 2043 | 93.767937 5.448903 93.525986 5.715712 93.211113 5.911261 c 2044 | 92.899559 6.106810 92.531662 6.204585 92.107422 6.204585 c 2045 | 91.640091 6.204585 91.235733 6.088581 90.894356 5.856573 c 2046 | 90.556290 5.627880 90.296104 5.329585 90.113815 4.961687 c 2047 | 89.931526 4.593789 89.840378 4.199376 89.840378 3.778448 c 2048 | 89.840378 3.102311 l 2049 | 89.840378 2.525607 89.939812 2.036735 90.138672 1.635693 c 2050 | 90.340851 1.237966 90.620918 0.934698 90.978874 0.725891 c 2051 | 91.336823 0.520398 91.752777 0.417652 92.226738 0.417652 c 2052 | 92.534973 0.417652 92.813385 0.460739 93.061966 0.546914 c 2053 | 93.313866 0.636402 93.530960 0.768978 93.713249 0.944641 c 2054 | 93.895538 1.123618 94.036400 1.345683 94.135834 1.610834 c 2055 | 95.269356 1.292652 l 2056 | 95.150040 0.908183 94.949516 0.570115 94.667793 0.278448 c 2057 | 94.386070 -0.009904 94.038055 -0.235283 93.623756 -0.397689 c 2058 | 93.209457 -0.556780 92.743790 -0.636325 92.226738 -0.636325 c 2059 | h 2060 | 100.037109 -0.477234 m 2061 | h 2062 | 101.110970 -3.340870 m 2063 | 101.110970 7.159130 l 2064 | 102.244499 7.159130 l 2065 | 102.244499 5.946062 l 2066 | 102.383698 5.946062 l 2067 | 102.469872 6.078638 102.589195 6.247672 102.741653 6.453164 c 2068 | 102.897430 6.661971 103.119492 6.847577 103.407845 7.009982 c 2069 | 103.699509 7.175702 104.093925 7.258562 104.591087 7.258562 c 2070 | 105.234077 7.258562 105.800842 7.097814 106.291374 6.776318 c 2071 | 106.781906 6.454821 107.164719 5.999092 107.439812 5.409130 c 2072 | 107.714905 4.819168 107.852448 4.123145 107.852448 3.321061 c 2073 | 107.852448 2.512349 107.714905 1.811355 107.439812 1.218079 c 2074 | 107.164719 0.628117 106.783562 0.170731 106.296341 -0.154080 c 2075 | 105.809128 -0.475576 105.247337 -0.636325 104.610970 -0.636325 c 2076 | 104.120438 -0.636325 103.727684 -0.555122 103.432709 -0.392716 c 2077 | 103.137726 -0.226997 102.910690 -0.039734 102.751595 0.169073 c 2078 | 102.592506 0.381194 102.469872 0.556857 102.383698 0.696061 c 2079 | 102.284271 0.696061 l 2080 | 102.284271 -3.340870 l 2081 | 101.110970 -3.340870 l 2082 | h 2083 | 102.264381 3.340948 m 2084 | 102.264381 2.764243 102.348900 2.255484 102.517937 1.814670 c 2085 | 102.686966 1.377170 102.933891 1.034130 103.258698 0.785550 c 2086 | 103.583511 0.540285 103.981239 0.417652 104.451881 0.417652 c 2087 | 104.942413 0.417652 105.351738 0.546913 105.679863 0.805436 c 2088 | 106.011307 1.067274 106.259888 1.418600 106.425606 1.859414 c 2089 | 106.594635 2.303543 106.679153 2.797387 106.679153 3.340948 c 2090 | 106.679153 3.877880 106.596291 4.361782 106.430573 4.792653 c 2091 | 106.268166 5.226838 106.021248 5.569878 105.689812 5.821772 c 2092 | 105.361687 6.076981 104.949043 6.204585 104.451881 6.204585 c 2093 | 103.974609 6.204585 103.573563 6.083609 103.248756 5.841658 c 2094 | 102.923950 5.603022 102.678680 5.268268 102.512962 4.837397 c 2095 | 102.347244 4.409841 102.264381 3.911024 102.264381 3.340948 c 2096 | h 2097 | 108.568359 -0.477234 m 2098 | h 2099 | 111.889381 -0.656211 m 2100 | 111.405479 -0.656211 110.966324 -0.565065 110.571907 -0.382773 c 2101 | 110.177498 -0.197167 109.864288 0.069641 109.632278 0.417652 c 2102 | 109.400276 0.768978 109.284271 1.193221 109.284271 1.690380 c 2103 | 109.284271 2.127880 109.370445 2.482520 109.542793 2.754300 c 2104 | 109.715141 3.029395 109.945488 3.244831 110.233841 3.400607 c 2105 | 110.522194 3.556383 110.840378 3.672387 111.188385 3.748618 c 2106 | 111.539711 3.828163 111.892700 3.891137 112.247337 3.937539 c 2107 | 112.711349 3.997198 113.087532 4.041943 113.375885 4.071772 c 2108 | 113.667549 4.104916 113.879677 4.159603 114.012253 4.235834 c 2109 | 114.148140 4.312065 114.216087 4.444641 114.216087 4.633562 c 2110 | 114.216087 4.673334 l 2111 | 114.216087 5.163865 114.081856 5.545020 113.813385 5.816800 c 2112 | 113.548233 6.088581 113.145538 6.224471 112.605293 6.224471 c 2113 | 112.045158 6.224471 111.606003 6.101838 111.287819 5.856573 c 2114 | 110.969635 5.611308 110.745918 5.349471 110.616653 5.071062 c 2115 | 109.503021 5.468789 l 2116 | 109.701881 5.932805 109.967033 6.294073 110.298470 6.552596 c 2117 | 110.633224 6.814433 110.997810 6.996725 111.392220 7.099471 c 2118 | 111.789948 7.205531 112.181053 7.258562 112.565521 7.258562 c 2119 | 112.810783 7.258562 113.092506 7.228732 113.410690 7.169074 c 2120 | 113.732185 7.112729 114.042084 6.995068 114.340378 6.816091 c 2121 | 114.641991 6.637113 114.892227 6.366990 115.091087 6.005721 c 2122 | 115.289948 5.644452 115.389381 5.160550 115.389381 4.554016 c 2123 | 115.389381 -0.477234 l 2124 | 114.216087 -0.477234 l 2125 | 114.216087 0.556857 l 2126 | 114.156425 0.556857 l 2127 | 114.076881 0.391137 113.944305 0.213817 113.758698 0.024897 c 2128 | 113.573097 -0.164023 113.326172 -0.324772 113.017937 -0.457348 c 2129 | 112.709694 -0.589923 112.333511 -0.656211 111.889381 -0.656211 c 2130 | h 2131 | 112.068359 0.397766 m 2132 | 112.532372 0.397766 112.923470 0.488912 113.241653 0.671204 c 2133 | 113.563148 0.853496 113.805099 1.088818 113.967506 1.377170 c 2134 | 114.133224 1.665522 114.216087 1.968789 114.216087 2.286971 c 2135 | 114.216087 3.360834 l 2136 | 114.166367 3.301175 114.056992 3.246488 113.887962 3.196772 c 2137 | 113.722244 3.150371 113.530006 3.108940 113.311256 3.072482 c 2138 | 113.095825 3.039338 112.885361 3.009508 112.679863 2.982993 c 2139 | 112.477684 2.959793 112.313622 2.939906 112.187675 2.923334 c 2140 | 111.882751 2.883561 111.597717 2.818931 111.332565 2.729443 c 2141 | 111.070732 2.643268 110.858604 2.512349 110.696198 2.336687 c 2142 | 110.537109 2.164338 110.457565 1.929016 110.457565 1.630721 c 2143 | 110.457565 1.223050 110.608368 0.914811 110.909981 0.706005 c 2144 | 111.214905 0.500512 111.601028 0.397766 112.068359 0.397766 c 2145 | h 2146 | 116.183594 -0.477234 m 2147 | h 2148 | 118.013138 -3.340870 m 2149 | 117.814278 -3.340870 117.636955 -3.324299 117.481178 -3.291155 c 2150 | 117.325401 -3.261326 117.217682 -3.231495 117.158028 -3.201666 c 2151 | 117.456322 -2.167575 l 2152 | 117.741364 -2.240492 117.993256 -2.267007 118.212006 -2.247120 c 2153 | 118.430756 -2.227234 118.624649 -2.129459 118.793678 -1.953796 c 2154 | 118.966026 -1.781447 119.123459 -1.501382 119.265984 -1.113598 c 2155 | 119.484734 -0.517007 l 2156 | 116.660866 7.159130 l 2157 | 117.933594 7.159130 l 2158 | 120.041550 1.073902 l 2159 | 120.121094 1.073902 l 2160 | 122.229050 7.159130 l 2161 | 123.501778 7.159130 l 2162 | 120.260300 -1.590870 l 2163 | 120.114464 -1.985283 119.933830 -2.311751 119.718391 -2.570274 c 2164 | 119.502960 -2.832111 119.252724 -3.026003 118.967682 -3.151949 c 2165 | 118.685959 -3.277897 118.367775 -3.340870 118.013138 -3.340870 c 2166 | h 2167 | 123.976562 -0.477234 m 2168 | h 2169 | 125.050423 -0.477234 m 2170 | 125.050423 7.159130 l 2171 | 126.183952 7.159130 l 2172 | 126.183952 5.965948 l 2173 | 126.283379 5.965948 l 2174 | 126.442474 6.373619 126.699341 6.690143 127.053978 6.915522 c 2175 | 127.408615 7.144215 127.834518 7.258562 128.331680 7.258562 c 2176 | 128.835464 7.258562 129.254730 7.144215 129.589493 6.915522 c 2177 | 129.927551 6.690143 130.191055 6.373619 130.379974 5.965948 c 2178 | 130.459518 5.965948 l 2179 | 130.655060 6.360361 130.948395 6.673571 131.339493 6.905579 c 2180 | 131.730591 7.140901 132.199570 7.258562 132.746445 7.258562 c 2181 | 133.429214 7.258562 133.987686 7.044783 134.421875 6.617227 c 2182 | 134.856064 6.192984 135.073151 5.531763 135.073151 4.633562 c 2183 | 135.073151 -0.477234 l 2184 | 133.899857 -0.477234 l 2185 | 133.899857 4.633562 l 2186 | 133.899857 5.197009 133.745743 5.599708 133.437500 5.841658 c 2187 | 133.129257 6.083609 132.766342 6.204585 132.348724 6.204585 c 2188 | 131.811783 6.204585 131.395828 6.042179 131.100845 5.717369 c 2189 | 130.805878 5.395872 130.658386 4.988202 130.658386 4.494357 c 2190 | 130.658386 -0.477234 l 2191 | 129.465195 -0.477234 l 2192 | 129.465195 4.752880 l 2193 | 129.465195 5.187066 129.324341 5.536734 129.042618 5.801886 c 2194 | 128.760895 6.070352 128.397964 6.204585 127.953835 6.204585 c 2195 | 127.648911 6.204585 127.363876 6.123382 127.098724 5.960977 c 2196 | 126.836884 5.798571 126.624763 5.573193 126.462357 5.284841 c 2197 | 126.303268 4.999803 126.223724 4.670020 126.223724 4.295493 c 2198 | 126.223724 -0.477234 l 2199 | 125.050423 -0.477234 l 2200 | h 2201 | 136.144531 -0.477234 m 2202 | h 2203 | 140.420105 -0.636325 m 2204 | 139.684311 -0.636325 139.049591 -0.473920 138.515976 -0.149109 c 2205 | 137.985672 0.179016 137.576340 0.636402 137.287994 1.223050 c 2206 | 137.002960 1.813012 136.860443 2.499092 136.860443 3.281289 c 2207 | 136.860443 4.063486 137.002960 4.752880 137.287994 5.349471 c 2208 | 137.576340 5.949376 137.977386 6.416706 138.491119 6.751460 c 2209 | 139.008163 7.089528 139.611389 7.258562 140.300781 7.258562 c 2210 | 140.698502 7.258562 141.091263 7.192274 141.479050 7.059699 c 2211 | 141.866837 6.927123 142.219818 6.711687 142.537994 6.413391 c 2212 | 142.856186 6.118410 143.109726 5.727312 143.298645 5.240096 c 2213 | 143.487564 4.752880 143.582031 4.152974 143.582031 3.440380 c 2214 | 143.582031 2.943221 l 2215 | 137.695663 2.943221 l 2216 | 137.695663 3.957425 l 2217 | 142.388855 3.957425 l 2218 | 142.388855 4.388296 142.302673 4.772766 142.130325 5.110834 c 2219 | 141.961288 5.448903 141.719345 5.715712 141.404480 5.911261 c 2220 | 141.092926 6.106810 140.725021 6.204585 140.300781 6.204585 c 2221 | 139.833450 6.204585 139.429092 6.088581 139.087708 5.856573 c 2222 | 138.749649 5.627880 138.489471 5.329585 138.307175 4.961687 c 2223 | 138.124878 4.593789 138.033737 4.199376 138.033737 3.778448 c 2224 | 138.033737 3.102311 l 2225 | 138.033737 2.525607 138.133163 2.036735 138.332031 1.635693 c 2226 | 138.534210 1.237966 138.814270 0.934698 139.172226 0.725891 c 2227 | 139.530182 0.520398 139.946152 0.417652 140.420105 0.417652 c 2228 | 140.728348 0.417652 141.006744 0.460739 141.255325 0.546914 c 2229 | 141.507217 0.636402 141.724304 0.768978 141.906601 0.944641 c 2230 | 142.088898 1.123618 142.229752 1.345683 142.329193 1.610834 c 2231 | 143.462708 1.292652 l 2232 | 143.343399 0.908183 143.142868 0.570115 142.861145 0.278448 c 2233 | 142.579422 -0.009904 142.231415 -0.235283 141.817123 -0.397689 c 2234 | 141.402817 -0.556780 140.937149 -0.636325 140.420105 -0.636325 c 2235 | h 2236 | 144.292969 -0.477234 m 2237 | h 2238 | 146.540131 4.116516 m 2239 | 146.540131 -0.477234 l 2240 | 145.366837 -0.477234 l 2241 | 145.366837 7.159130 l 2242 | 146.500351 7.159130 l 2243 | 146.500351 5.965948 l 2244 | 146.599792 5.965948 l 2245 | 146.778763 6.353732 147.050552 6.665285 147.415131 6.900607 c 2246 | 147.779709 7.139244 148.250351 7.258562 148.827057 7.258562 c 2247 | 149.344101 7.258562 149.796524 7.152501 150.184311 6.940380 c 2248 | 150.572083 6.731573 150.873688 6.413391 151.089127 5.985835 c 2249 | 151.304565 5.561592 151.412292 5.024660 151.412292 4.375039 c 2250 | 151.412292 -0.477234 l 2251 | 150.238998 -0.477234 l 2252 | 150.238998 4.295493 l 2253 | 150.238998 4.895399 150.083221 5.362728 149.771667 5.697482 c 2254 | 149.460114 6.035551 149.032562 6.204585 148.488998 6.204585 c 2255 | 148.114471 6.204585 147.779709 6.123382 147.484726 5.960977 c 2256 | 147.193054 5.798571 146.962708 5.561592 146.793686 5.250039 c 2257 | 146.624649 4.938486 146.540131 4.560645 146.540131 4.116516 c 2258 | h 2259 | 152.482422 -0.477234 m 2260 | h 2261 | 156.877304 7.159130 m 2262 | 156.877304 6.164812 l 2263 | 152.919922 6.164812 l 2264 | 152.919922 7.159130 l 2265 | 156.877304 7.159130 l 2266 | h 2267 | 154.073334 8.988675 m 2268 | 155.246628 8.988675 l 2269 | 155.246628 1.710266 l 2270 | 155.246628 1.378827 155.294693 1.130247 155.390808 0.964527 c 2271 | 155.490234 0.802122 155.616180 0.692747 155.768646 0.636402 c 2272 | 155.924423 0.583372 156.088486 0.556857 156.260834 0.556857 c 2273 | 156.390091 0.556857 156.496155 0.563486 156.579010 0.576743 c 2274 | 156.661880 0.593315 156.728165 0.606573 156.777878 0.616516 c 2275 | 157.016510 -0.437461 l 2276 | 156.936966 -0.467291 156.825943 -0.497120 156.683411 -0.526950 c 2277 | 156.540894 -0.560094 156.360260 -0.576666 156.141510 -0.576666 c 2278 | 155.810074 -0.576666 155.485260 -0.505406 155.167084 -0.362887 c 2279 | 154.852219 -0.220368 154.590378 -0.003276 154.381577 0.288391 c 2280 | 154.176086 0.580058 154.073334 0.947955 154.073334 1.392084 c 2281 | 154.073334 8.988675 l 2282 | h 2283 | 157.568359 -0.477234 m 2284 | h 2285 | 159.497330 -0.556779 m 2286 | 159.252075 -0.556779 159.041611 -0.468947 158.865952 -0.293285 c 2287 | 158.690277 -0.117622 158.602448 0.092842 158.602448 0.338107 c 2288 | 158.602448 0.583372 158.690277 0.793837 158.865952 0.969499 c 2289 | 159.041611 1.145162 159.252075 1.232993 159.497330 1.232993 c 2290 | 159.742599 1.232993 159.953064 1.145162 160.128723 0.969499 c 2291 | 160.304398 0.793837 160.392227 0.583372 160.392227 0.338107 c 2292 | 160.392227 0.175701 160.350800 0.026554 160.267929 -0.109336 c 2293 | 160.188385 -0.245226 160.080673 -0.354601 159.944778 -0.437461 c 2294 | 159.812195 -0.517006 159.663055 -0.556779 159.497330 -0.556779 c 2295 | h 2296 | f 2297 | n 2298 | Q 2299 | q 2300 | 1.000000 0.000000 -0.000000 1.000000 54.000000 513.477234 cm 2301 | BT 2302 | 14.000000 0.000000 0.000000 14.000000 0.000000 -0.477234 Tm 2303 | /F1 1.000000 Tf 2304 | [ (\040) (\034) (\033) (\031) (\032) (\027) (\030) (\026) 22.904805 (\036) (\035) (\030) (\025) -11.363438 (\034) (\023) (\030) (\037) (\033) 19.975390 (\022) (\024) (\023) (\031) (\025) (\021) ] TJ 2305 | ET 2306 | Q 2307 | q 2308 | 1.000000 0.000000 -0.000000 1.000000 54.000000 54.000000 cm 2309 | 0.112500 0.112500 0.112500 scn 2310 | 0.000000 406.000000 m 2311 | 0.000000 409.313721 2.686292 412.000000 6.000000 412.000000 c 2312 | 386.000000 412.000000 l 2313 | 389.313721 412.000000 392.000000 409.313721 392.000000 406.000000 c 2314 | 392.000000 6.000000 l 2315 | 392.000000 2.686279 389.313721 0.000000 386.000000 0.000000 c 2316 | 5.999997 0.000000 l 2317 | 2.686289 0.000000 0.000000 2.686279 0.000000 6.000000 c 2318 | 0.000000 406.000000 l 2319 | h 2320 | f 2321 | n 2322 | Q 2323 | 2324 | endstream 2325 | endobj 2326 | 2327 | 89 0 obj 2328 | 59172 2329 | endobj 2330 | 2331 | 90 0 obj 2332 | << /Annots [] 2333 | /Type /Page 2334 | /MediaBox [ 0.000000 0.000000 500.000000 700.000000 ] 2335 | /Resources 87 0 R 2336 | /Contents 88 0 R 2337 | /Parent 91 0 R 2338 | >> 2339 | endobj 2340 | 2341 | 91 0 obj 2342 | << /Kids [ 90 0 R ] 2343 | /Count 1 2344 | /Type /Pages 2345 | >> 2346 | endobj 2347 | 2348 | 92 0 obj 2349 | << /Pages 91 0 R 2350 | /Type /Catalog 2351 | >> 2352 | endobj 2353 | 2354 | xref 2355 | 0 93 2356 | 0000000000 65535 f 2357 | 0000000010 00000 n 2358 | 0000000117 00000 n 2359 | 0000000138 00000 n 2360 | 0000000245 00000 n 2361 | 0000000266 00000 n 2362 | 0000000372 00000 n 2363 | 0000000393 00000 n 2364 | 0000000500 00000 n 2365 | 0000000521 00000 n 2366 | 0000000628 00000 n 2367 | 0000000650 00000 n 2368 | 0000000759 00000 n 2369 | 0000000781 00000 n 2370 | 0000000889 00000 n 2371 | 0000000911 00000 n 2372 | 0000001019 00000 n 2373 | 0000001041 00000 n 2374 | 0000001149 00000 n 2375 | 0000001171 00000 n 2376 | 0000001280 00000 n 2377 | 0000001302 00000 n 2378 | 0000001411 00000 n 2379 | 0000001433 00000 n 2380 | 0000001541 00000 n 2381 | 0000001563 00000 n 2382 | 0000001672 00000 n 2383 | 0000001694 00000 n 2384 | 0000001802 00000 n 2385 | 0000001824 00000 n 2386 | 0000001933 00000 n 2387 | 0000001955 00000 n 2388 | 0000002063 00000 n 2389 | 0000002085 00000 n 2390 | 0000002194 00000 n 2391 | 0000002216 00000 n 2392 | 0000002324 00000 n 2393 | 0000002346 00000 n 2394 | 0000002454 00000 n 2395 | 0000002476 00000 n 2396 | 0000002584 00000 n 2397 | 0000002606 00000 n 2398 | 0000002714 00000 n 2399 | 0000002736 00000 n 2400 | 0000002845 00000 n 2401 | 0000002867 00000 n 2402 | 0000002976 00000 n 2403 | 0000002998 00000 n 2404 | 0000003106 00000 n 2405 | 0000003128 00000 n 2406 | 0000003237 00000 n 2407 | 0000003259 00000 n 2408 | 0000003368 00000 n 2409 | 0000003390 00000 n 2410 | 0000003499 00000 n 2411 | 0000003521 00000 n 2412 | 0000003629 00000 n 2413 | 0000003651 00000 n 2414 | 0000003760 00000 n 2415 | 0000003782 00000 n 2416 | 0000003891 00000 n 2417 | 0000003913 00000 n 2418 | 0000004021 00000 n 2419 | 0000004043 00000 n 2420 | 0000004151 00000 n 2421 | 0000004173 00000 n 2422 | 0000004281 00000 n 2423 | 0000004303 00000 n 2424 | 0000004623 00000 n 2425 | 0000006165 00000 n 2426 | 0000006189 00000 n 2427 | 0000007751 00000 n 2428 | 0000008050 00000 n 2429 | 0000008073 00000 n 2430 | 0000010230 00000 n 2431 | 0000010254 00000 n 2432 | 0000010553 00000 n 2433 | 0000010576 00000 n 2434 | 0000013851 00000 n 2435 | 0000013875 00000 n 2436 | 0000014397 00000 n 2437 | 0000014420 00000 n 2438 | 0000014942 00000 n 2439 | 0000014965 00000 n 2440 | 0000015687 00000 n 2441 | 0000015710 00000 n 2442 | 0000016432 00000 n 2443 | 0000016455 00000 n 2444 | 0000019462 00000 n 2445 | 0000078692 00000 n 2446 | 0000078717 00000 n 2447 | 0000078896 00000 n 2448 | 0000078972 00000 n 2449 | trailer 2450 | << /ID [ (some) (id) ] 2451 | /Root 92 0 R 2452 | /Size 93 2453 | >> 2454 | startxref 2455 | 79033 2456 | %%EOF -------------------------------------------------------------------------------- /sig/resend.rbs: -------------------------------------------------------------------------------- 1 | module Resend 2 | VERSION: String 3 | # See the writing guide of rbs: https://github.com/ruby/rbs#guides 4 | end 5 | -------------------------------------------------------------------------------- /spec/api_keys_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe "API Keys" do 4 | context 'static api_key' do 5 | before do 6 | Resend.configure do |config| 7 | config.api_key = "re_123" 8 | end 9 | end 10 | 11 | describe "create" do 12 | 13 | it "should create api key" do 14 | resp = { 15 | "id": "dacf4072-4119-4d88-932f-6202748ac7c8", 16 | "token": "re_c1tpEyD8_NKFusih9vKVQknRAQfmFcWCv" 17 | } 18 | params = { 19 | "name": "production" 20 | } 21 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 22 | expect(Resend::ApiKeys.create(params)[:id]).to eql("dacf4072-4119-4d88-932f-6202748ac7c8") 23 | end 24 | 25 | it "should raise when permission is invalid" do 26 | resp = { 27 | "statusCode" => 422, 28 | "name" => "invalid_permission", 29 | "message" => "Access must be 'full_access' | 'sending_access'" 30 | } 31 | allow(resp).to receive(:body).and_return(resp) 32 | params = { 33 | "name": "production", 34 | "permission": "invalid" 35 | } 36 | allow(HTTParty).to receive(:send).and_return(resp) 37 | expect { Resend::ApiKeys.create params }.to raise_error(Resend::Error::InvalidRequestError, /Access must be 'full_access' | 'sending_access'/) 38 | end 39 | end 40 | 41 | describe "list" do 42 | it "should list api keys" do 43 | resp = { 44 | "data": [ 45 | { 46 | "id":"6e3c3d83-05dc-4b51-acfc-fe8972738bd0", 47 | "name":"test1", 48 | "created_at":"2023-04-21T01:31:02.671414+00:00" 49 | } 50 | ] 51 | } 52 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 53 | expect(Resend::ApiKeys.list.length).to eql(1) 54 | end 55 | end 56 | 57 | describe "remove" do 58 | it "should remove api key" do 59 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return("") 60 | expect { Resend::ApiKeys.remove }.not_to raise_error 61 | end 62 | end 63 | end 64 | 65 | context 'dynamic api_key' do 66 | before do 67 | Resend.configure do |config| 68 | config.api_key = -> { "re_123" } 69 | end 70 | end 71 | 72 | describe "create" do 73 | 74 | it "should create api key" do 75 | resp = { 76 | "id": "dacf4072-4119-4d88-932f-6202748ac7c8", 77 | "token": "re_c1tpEyD8_NKFusih9vKVQknRAQfmFcWCv" 78 | } 79 | params = { 80 | "name": "production" 81 | } 82 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 83 | expect(Resend::ApiKeys.create(params)[:id]).to eql("dacf4072-4119-4d88-932f-6202748ac7c8") 84 | end 85 | 86 | it "should raise when permission is invalid" do 87 | resp = { 88 | "statusCode" => 422, 89 | "name" => "invalid_permission", 90 | "message" => "Access must be 'full_access' | 'sending_access'" 91 | } 92 | allow(resp).to receive(:body).and_return(resp) 93 | params = { 94 | "name": "production", 95 | "permission": "invalid" 96 | } 97 | allow(HTTParty).to receive(:send).and_return(resp) 98 | expect { Resend::ApiKeys.create params }.to raise_error(Resend::Error::InvalidRequestError, /Access must be 'full_access' | 'sending_access'/) 99 | end 100 | end 101 | 102 | describe "list" do 103 | it "should list api keys" do 104 | resp = { 105 | "data": [ 106 | { 107 | "id":"6e3c3d83-05dc-4b51-acfc-fe8972738bd0", 108 | "name":"test1", 109 | "created_at":"2023-04-21T01:31:02.671414+00:00" 110 | } 111 | ] 112 | } 113 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 114 | expect(Resend::ApiKeys.list.length).to eql(1) 115 | end 116 | end 117 | 118 | describe "remove" do 119 | it "should remove api key" do 120 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return("") 121 | expect { Resend::ApiKeys.remove }.not_to raise_error 122 | end 123 | end 124 | end 125 | end 126 | -------------------------------------------------------------------------------- /spec/audiences_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe "Audiences" do 4 | 5 | describe "create audience" do 6 | 7 | before do 8 | Resend.configure do |config| 9 | config.api_key = "re_123" 10 | end 11 | end 12 | 13 | it "should create an audience record" do 14 | resp = { 15 | "object": "audience", 16 | "id": "78261eea-8f8b-4381-83c6-79fa7120f1cf", 17 | "name": "Registered Users" 18 | } 19 | params = { 20 | "name": "Registered Users", 21 | } 22 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 23 | audience = Resend::Audiences.create(params) 24 | expect(audience[:id]).to eql("78261eea-8f8b-4381-83c6-79fa7120f1cf") 25 | expect(audience[:name]).to eql("Registered Users") 26 | expect(audience[:object]).to eql("audience") 27 | end 28 | end 29 | 30 | describe "get audience" do 31 | it "should retrieve audience" do 32 | resp = { 33 | "object": "audience", 34 | "id": "78261eea-8f8b-4381-83c6-79fa7120f1cf", 35 | "name": "Registered Users", 36 | "created_at": "2023-10-06T22:59:55.977Z" 37 | } 38 | allow(resp).to receive(:body).and_return(resp) 39 | allow(HTTParty).to receive(:send).and_return(resp) 40 | 41 | audience = Resend::Audiences.get(resp[:id]) 42 | 43 | expect(audience[:object]).to eql "audience" 44 | expect(audience[:id]).to eql "78261eea-8f8b-4381-83c6-79fa7120f1cf" 45 | expect(audience[:name]).to eql "Registered Users" 46 | expect(audience[:created_at]).to eql "2023-10-06T22:59:55.977Z" 47 | end 48 | end 49 | 50 | describe "list audiences" do 51 | it "should list audiences" do 52 | resp = { 53 | "object": "list", 54 | "data": [ 55 | { 56 | "id": "78261eea-8f8b-4381-83c6-79fa7120f1cf", 57 | "name": "Registered Users", 58 | "created_at": "2023-10-06T22:59:55.977Z" 59 | } 60 | ] 61 | } 62 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 63 | audiences = Resend::Audiences.list 64 | expect(audiences[:object]).to eql "list" 65 | expect(audiences[:data].empty?).to be false 66 | expect(audiences[:data].length).to eql(1) 67 | expect(audiences[:data].first[:id]).to eql("78261eea-8f8b-4381-83c6-79fa7120f1cf") 68 | expect(audiences[:data].first[:name]).to eql("Registered Users") 69 | end 70 | end 71 | 72 | describe "remove audience" do 73 | it "should remove audience by id" do 74 | 75 | resp = { 76 | "object": "audience", 77 | "id": "78261eea-8f8b-4381-83c6-79fa7120f1cf", 78 | "deleted": true 79 | } 80 | 81 | allow(resp).to receive(:body).and_return(resp) 82 | allow(HTTParty).to receive(:send).and_return(resp) 83 | 84 | deleted = Resend::Audiences.remove("78261eea-8f8b-4381-83c6-79fa7120f1cf") 85 | expect(deleted[:object]).to eql("audience") 86 | expect(deleted[:id]).to eql("78261eea-8f8b-4381-83c6-79fa7120f1cf") 87 | expect(deleted[:deleted]).to be true 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/batch_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe "Batch" do 4 | describe "#send" do 5 | 6 | before do 7 | Resend.api_key = "re_123" 8 | end 9 | 10 | it "should send batch email" do 11 | resp = { 12 | "data": [ 13 | { 14 | "id": "ae2014de-c168-4c61-8267-70d2662a1ce1" 15 | }, 16 | { 17 | "id": "faccb7a5-8a28-4e9a-ac64-8da1cc3bc1cb" 18 | } 19 | ] 20 | } 21 | 22 | params = [ 23 | { 24 | "from": "from@e.io", 25 | "to": ["email1@email.com"], 26 | "text": "testing", 27 | "subject": "Hey", 28 | }, 29 | { 30 | "from": "from@e.io", 31 | "to": ["email2@email.com"], 32 | "text": "testing", 33 | "subject": "Hello", 34 | }, 35 | ] 36 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 37 | emails = Resend::Batch.send(params) 38 | expect(emails[:data].length).to eq 2 39 | end 40 | 41 | it "does not send the Idempotency-Key header when :idempotency_key is not provided" do 42 | resp = { 43 | "id"=>"872d1f17-0f08-424c-a18c-d425324acab6", "object": "email" 44 | } 45 | 46 | allow(resp).to receive(:body).and_return(resp) 47 | allow(HTTParty).to receive(:send).and_return(resp) 48 | 49 | Resend::Batch.send([{ from: "me" }], options: {}) 50 | 51 | expect(HTTParty).to have_received(:send).with( 52 | :post, 53 | "#{Resend::Request::BASE_URL}emails/batch", 54 | { 55 | headers: { 56 | "Content-Type" => "application/json", 57 | "Accept" => "application/json", 58 | "Authorization" => "Bearer re_123", 59 | "User-Agent" => "resend-ruby:#{Resend::VERSION}", 60 | }, 61 | body: [{ from: "me" }].to_json 62 | } 63 | ) 64 | end 65 | 66 | it "does not send the Idempotency-Key header when :idempotency_key is nil" do 67 | resp = { 68 | "id"=>"872d1f17-0f08-424c-a18c-d425324acab6", "object": "email" 69 | } 70 | 71 | allow(resp).to receive(:body).and_return(resp) 72 | allow(HTTParty).to receive(:send).and_return(resp) 73 | 74 | Resend::Batch.send([{ from: "me" }], options: { idempotency_key: nil }) 75 | 76 | expect(HTTParty).to have_received(:send).with( 77 | :post, 78 | "#{Resend::Request::BASE_URL}emails/batch", 79 | { 80 | headers: { 81 | "Content-Type" => "application/json", 82 | "Accept" => "application/json", 83 | "Authorization" => "Bearer re_123", 84 | "User-Agent" => "resend-ruby:#{Resend::VERSION}", 85 | }, 86 | body: [{ from: "me" }].to_json 87 | } 88 | ) 89 | end 90 | 91 | it "does not send the Idempotency-Key header when :idempotency_key is an empty string" do 92 | resp = { 93 | "id"=>"872d1f17-0f08-424c-a18c-d425324acab6", "object": "email" 94 | } 95 | 96 | allow(resp).to receive(:body).and_return(resp) 97 | allow(HTTParty).to receive(:send).and_return(resp) 98 | 99 | Resend::Batch.send([{ from: "me" }], options: { idempotency_key: "" }) 100 | 101 | expect(HTTParty).to have_received(:send).with( 102 | :post, 103 | "#{Resend::Request::BASE_URL}emails/batch", 104 | { 105 | headers: { 106 | "Content-Type" => "application/json", 107 | "Accept" => "application/json", 108 | "Authorization" => "Bearer re_123", 109 | "User-Agent" => "resend-ruby:#{Resend::VERSION}", 110 | }, 111 | body: [{ from: "me" }].to_json 112 | } 113 | ) 114 | end 115 | 116 | it "does send the Idempotency-Key header when :idempotency_key is provided" do 117 | resp = { 118 | "id"=>"872d1f17-0f08-424c-a18c-d425324acab6", "object": "email" 119 | } 120 | 121 | allow(resp).to receive(:body).and_return(resp) 122 | allow(HTTParty).to receive(:send).and_return(resp) 123 | 124 | Resend::Batch.send([{ from: "me" }], options: { idempotency_key: "123" }) 125 | 126 | expect(HTTParty).to have_received(:send).with( 127 | :post, 128 | "#{Resend::Request::BASE_URL}emails/batch", 129 | { 130 | headers: { 131 | "Content-Type" => "application/json", 132 | "Accept" => "application/json", 133 | "Authorization" => "Bearer re_123", 134 | "User-Agent" => "resend-ruby:#{Resend::VERSION}", 135 | "Idempotency-Key" => "123" 136 | }, 137 | body: [{ from: "me" }].to_json 138 | } 139 | ) 140 | end 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /spec/broadcasts_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe "Broadcasts" do 4 | 5 | before do 6 | Resend.configure do |config| 7 | config.api_key = "re_123" 8 | end 9 | end 10 | 11 | describe "create" do 12 | it "should create broadcast" do 13 | resp = { 14 | "id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794" 15 | } 16 | params = { 17 | "audience_id": "123123" 18 | } 19 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 20 | expect(Resend::Broadcasts.create(params)[:id]).to eql("49a3999c-0ce1-4ea6-ab68-afcd6dc2e794") 21 | end 22 | end 23 | 24 | describe "update" do 25 | it "should update broadcast" do 26 | resp = { 27 | "id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794" 28 | } 29 | params = { 30 | "broadcast_id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794" 31 | } 32 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 33 | expect(Resend::Broadcasts.update(params)[:id]).to eql("49a3999c-0ce1-4ea6-ab68-afcd6dc2e794") 34 | end 35 | end 36 | 37 | describe "send" do 38 | it "should send broadcast" do 39 | resp = { 40 | "id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794" 41 | } 42 | params = { 43 | "broadcast_id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794", 44 | "scheduled_at": "in 1 min" 45 | } 46 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 47 | expect(Resend::Broadcasts.send(params)[:id]).to eql("49a3999c-0ce1-4ea6-ab68-afcd6dc2e794") 48 | end 49 | end 50 | 51 | describe "list" do 52 | it "should list broadcasts" do 53 | resp = { 54 | "object": "list", 55 | "data": [ 56 | { 57 | "id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794", 58 | "audience_id": "78261eea-8f8b-4381-83c6-79fa7120f1cf", 59 | "status": "draft", 60 | "created_at": "2024-11-01T15:13:31.723Z", 61 | "scheduled_at": nil, 62 | "sent_at": nil 63 | }, 64 | { 65 | "id": "559ac32e-9ef5-46fb-82a1-b76b840c0f7b", 66 | "audience_id": "78261eea-8f8b-4381-83c6-79fa7120f1cf", 67 | "status": "sent", 68 | "created_at": "2024-12-01T19:32:22.980Z", 69 | "scheduled_at": "2024-12-02T19:32:22.980Z", 70 | "sent_at": "2024-12-02T19:32:22.980Z" 71 | } 72 | ] 73 | } 74 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 75 | 76 | broadcasts = Resend::Broadcasts.list[:data] 77 | 78 | expect(broadcasts.length).to eql(2) 79 | 80 | expect(broadcasts[0][:id]).to eql("49a3999c-0ce1-4ea6-ab68-afcd6dc2e794") 81 | expect(broadcasts[0][:audience_id]).to eql("78261eea-8f8b-4381-83c6-79fa7120f1cf") 82 | expect(broadcasts[0][:status]).to eql("draft") 83 | expect(broadcasts[0][:created_at]).to eql("2024-11-01T15:13:31.723Z") 84 | expect(broadcasts[0][:scheduled_at]).to eql(nil) 85 | expect(broadcasts[0][:sent_at]).to eql(nil) 86 | 87 | expect(broadcasts[1][:id]).to eql("559ac32e-9ef5-46fb-82a1-b76b840c0f7b") 88 | expect(broadcasts[1][:audience_id]).to eql("78261eea-8f8b-4381-83c6-79fa7120f1cf") 89 | expect(broadcasts[1][:status]).to eql("sent") 90 | expect(broadcasts[1][:created_at]).to eql("2024-12-01T19:32:22.980Z") 91 | expect(broadcasts[1][:scheduled_at]).to eql("2024-12-02T19:32:22.980Z") 92 | expect(broadcasts[1][:sent_at]).to eql("2024-12-02T19:32:22.980Z") 93 | end 94 | end 95 | 96 | describe "remove" do 97 | it "should remove broadcast" do 98 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return("") 99 | expect { Resend::Broadcasts.remove }.not_to raise_error 100 | end 101 | end 102 | 103 | describe "get broadcast" do 104 | 105 | it "should retrieve a broadcast" do 106 | 107 | resp = { 108 | "object": "broadcast", 109 | "id": "559ac32e-9ef5-46fb-82a1-b76b840c0f7b", 110 | "name": "Announcements", 111 | "audience_id": "78261eea-8f8b-4381-83c6-79fa7120f1cf", 112 | "from": "Acme ", 113 | "subject": "hello world", 114 | "reply_to": nil, 115 | "preview_text": "Check out our latest announcements", 116 | "status": "draft", 117 | "created_at": "2024-12-01T19:32:22.980Z", 118 | "scheduled_at": nil, 119 | "sent_at": nil 120 | } 121 | 122 | allow(resp).to receive(:body).and_return(resp) 123 | allow(HTTParty).to receive(:send).and_return(resp) 124 | 125 | broadcast = Resend::Broadcasts.get("559ac32e-9ef5-46fb-82a1-b76b840c0f7b") 126 | 127 | expect(broadcast[:object]).to eql "broadcast" 128 | expect(broadcast[:id]).to eql "559ac32e-9ef5-46fb-82a1-b76b840c0f7b" 129 | expect(broadcast[:name]).to eql "Announcements" 130 | expect(broadcast[:audience_id]).to eql "78261eea-8f8b-4381-83c6-79fa7120f1cf" 131 | expect(broadcast[:from]).to eql "Acme " 132 | expect(broadcast[:subject]).to eql "hello world" 133 | expect(broadcast[:reply_to]).to eql nil 134 | expect(broadcast[:preview_text]).to eql "Check out our latest announcements" 135 | expect(broadcast[:status]).to eql "draft" 136 | expect(broadcast[:created_at]).to eql "2024-12-01T19:32:22.980Z" 137 | expect(broadcast[:scheduled_at]).to eql nil 138 | expect(broadcast[:sent_at]).to eql nil 139 | end 140 | end 141 | 142 | end 143 | -------------------------------------------------------------------------------- /spec/contacts_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe "Contacts" do 4 | 5 | let(:audience_id) { "48c269ed-9873-4d60-bdd9-cd7e6fc0b9b8" } 6 | 7 | describe "create contact" do 8 | 9 | before do 10 | Resend.configure do |config| 11 | config.api_key = "re_123" 12 | end 13 | end 14 | 15 | it "should create a contact record" do 16 | resp = { 17 | "object": "contact", 18 | "id": "479e3145-dd38-476b-932c-529ceb705947" 19 | } 20 | 21 | params = { 22 | audience_id: audience_id, 23 | email: "steve@woz.com", 24 | first_name: "Steve", 25 | last_name: "Woz", 26 | unsubscribed: false, 27 | } 28 | 29 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 30 | contact = Resend::Contacts.create(params) 31 | expect(contact[:id]).to eql("479e3145-dd38-476b-932c-529ceb705947") 32 | expect(contact[:object]).to eql("contact") 33 | end 34 | end 35 | 36 | describe "get contact" do 37 | 38 | it "should retrieve a contact by id" do 39 | 40 | resp = { 41 | "object": "contact", 42 | "id": "e169aa45-1ecf-4183-9955-b1499d5701d3", 43 | "email": "steve.wozniak@gmail.com", 44 | "first_name": "Steve", 45 | "last_name": "Wozniak", 46 | "created_at": "2023-10-06T23:47:56.678Z", 47 | "unsubscribed": false 48 | } 49 | 50 | allow(resp).to receive(:body).and_return(resp) 51 | allow(HTTParty).to receive(:send).and_return(resp) 52 | 53 | contact = Resend::Contacts.get(audience_id, resp[:id]) 54 | 55 | expect(contact[:object]).to eql "contact" 56 | expect(contact[:id]).to eql "e169aa45-1ecf-4183-9955-b1499d5701d3" 57 | expect(contact[:first_name]).to eql "Steve" 58 | expect(contact[:last_name]).to eql "Wozniak" 59 | expect(contact[:email]).to eql "steve.wozniak@gmail.com" 60 | expect(contact[:unsubscribed]).to be false 61 | expect(contact[:created_at]).to eql "2023-10-06T23:47:56.678Z" 62 | end 63 | 64 | it "should retrieve a contact by email" do 65 | resp = { 66 | "object": "contact", 67 | "id": "e169aa45-1ecf-4183-9955-b1499d5701d3", 68 | "email": "steve.wozniak@gmail.com", 69 | "first_name": "Steve", 70 | "last_name": "Wozniak", 71 | "created_at": "2023-10-06T23:47:56.678Z", 72 | "unsubscribed": false 73 | } 74 | 75 | allow(resp).to receive(:body).and_return(resp) 76 | allow(HTTParty).to receive(:send).and_return(resp) 77 | 78 | contact = Resend::Contacts.get(audience_id, resp[:email]) 79 | 80 | expect(contact[:object]).to eql "contact" 81 | expect(contact[:id]).to eql "e169aa45-1ecf-4183-9955-b1499d5701d3" 82 | expect(contact[:first_name]).to eql "Steve" 83 | expect(contact[:last_name]).to eql "Wozniak" 84 | expect(contact[:email]).to eql "steve.wozniak@gmail.com" 85 | expect(contact[:unsubscribed]).to be false 86 | expect(contact[:created_at]).to eql "2023-10-06T23:47:56.678Z" 87 | end 88 | end 89 | 90 | describe "list contacts" do 91 | 92 | it "should list contacts" do 93 | 94 | resp = { 95 | "object": "list", 96 | "data": [ 97 | { 98 | "id": "e169aa45-1ecf-4183-9955-b1499d5701d3", 99 | "email": "steve.wozniak@gmail.com", 100 | "first_name": "Steve", 101 | "last_name": "Wozniak", 102 | "created_at": "2023-10-06T23:47:56.678Z", 103 | "unsubscribed": false 104 | } 105 | ] 106 | } 107 | 108 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 109 | contacts = Resend::Contacts.list(audience_id) 110 | expect(contacts[:object]).to eql "list" 111 | expect(contacts[:data].length).to eql 1 112 | expect(contacts[:data][0][:id]).to eql "e169aa45-1ecf-4183-9955-b1499d5701d3" 113 | expect(contacts[:data][0][:first_name]).to eql "Steve" 114 | expect(contacts[:data][0][:last_name]).to eql "Wozniak" 115 | expect(contacts[:data][0][:email]).to eql "steve.wozniak@gmail.com" 116 | expect(contacts[:data][0][:unsubscribed]).to be false 117 | end 118 | end 119 | 120 | describe "remove contact" do 121 | it "should remove contact by id" do 122 | 123 | resp = { 124 | "object": "contact", 125 | "id": "520784e2-887d-4c25-b53c-4ad46ad38100", 126 | "deleted": true 127 | } 128 | 129 | allow(resp).to receive(:body).and_return(resp) 130 | allow(HTTParty).to receive(:send).and_return(resp) 131 | 132 | deleted = Resend::Contacts.remove(audience_id, resp[:id]) 133 | expect(deleted[:object]).to eql("contact") 134 | expect(deleted[:id]).to eql(resp[:id]) 135 | expect(deleted[:deleted]).to be true 136 | end 137 | end 138 | 139 | describe "update contact" do 140 | 141 | before do 142 | Resend.configure do |config| 143 | config.api_key = "re_123" 144 | end 145 | end 146 | 147 | it "should update a contact record" do 148 | resp = { 149 | "object": "contact", 150 | "id": "479e3145-dd38-476b-932c-529ceb705947" 151 | } 152 | 153 | update_params = { 154 | audience_id: audience_id, 155 | id: "479e3145-dd38-476b-932c-529ceb705947", 156 | unsubscribed: false, 157 | } 158 | 159 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 160 | contact = Resend::Contacts.update(update_params) 161 | expect(contact[:id]).to eql("479e3145-dd38-476b-932c-529ceb705947") 162 | expect(contact[:object]).to eql("contact") 163 | end 164 | 165 | it "raise when required fields are not provided" do 166 | begin 167 | Resend::Contacts.update({ audience_id: "123" }) 168 | rescue ArgumentError => e 169 | expect(e.message).to eql("id or email is required") 170 | end 171 | end 172 | end 173 | end 174 | -------------------------------------------------------------------------------- /spec/domains_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe "Domains" do 4 | 5 | describe "create domain" do 6 | 7 | before do 8 | Resend.configure do |config| 9 | config.api_key = "re_123" 10 | end 11 | end 12 | 13 | it "should create domain record" do 14 | resp = { 15 | "id": "4dd369bc-aa82-4ff3-97de-514ae3000ee0", 16 | "name": "example.com", 17 | "createdAt": "2023-03-28T17:12:02.059593+00:00", 18 | "status": "not_started", 19 | "records": [ 20 | { 21 | "record": "SPF", 22 | "name": "bounces", 23 | "type": "MX", 24 | "ttl": "Auto", 25 | "status": "not_started", 26 | "value": "feedback-smtp.us-east-1.amazonses.com", 27 | "priority": 10 28 | }, 29 | { 30 | "record": "SPF", 31 | "name": "bounces", 32 | "value": "\"v=spf1 include:amazonses.com ~all\"", 33 | "type": "TXT", 34 | "ttl": "Auto", 35 | "status": "not_started" 36 | }, 37 | { 38 | "record": "DKIM", 39 | "name": "nhapbbryle57yxg3fbjytyodgbt2kyyg._domainkey", 40 | "value": "nhapbbryle57yxg3fbjytyodgbt2kyyg.dkim.amazonses.com.", 41 | "type": "CNAME", 42 | "status": "not_started", 43 | "ttl": "Auto" 44 | }, 45 | { 46 | "record": "DKIM", 47 | "name": "xbakwbe5fcscrhzshpap6kbxesf6pfgn._domainkey", 48 | "value": "xbakwbe5fcscrhzshpap6kbxesf6pfgn.dkim.amazonses.com.", 49 | "type": "CNAME", 50 | "status": "not_started", 51 | "ttl": "Auto" 52 | }, 53 | { 54 | "record": "DKIM", 55 | "name": "txrcreso3dqbvcve45tqyosxwaegvhgn._domainkey", 56 | "value": "txrcreso3dqbvcve45tqyosxwaegvhgn.dkim.amazonses.com.", 57 | "type": "CNAME", 58 | "status": "not_started", 59 | "ttl": "Auto" 60 | } 61 | ], 62 | "region": "us-east-1", 63 | "dnsProvider": "Unidentified" 64 | } 65 | params = { 66 | "name": "example.com", 67 | } 68 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 69 | expect(Resend::Domains.create(params)[:id]).to eql("4dd369bc-aa82-4ff3-97de-514ae3000ee0") 70 | end 71 | 72 | it "should raise when domain is already registered" do 73 | resp = { 74 | "name"=>"validation_error", 75 | "message"=>"The name domain has been registered already.", 76 | "statusCode"=>400 77 | } 78 | allow(resp).to receive(:body).and_return(resp) 79 | params = { 80 | "name": "example.com", 81 | } 82 | allow(HTTParty).to receive(:send).and_return(resp) 83 | expect { Resend::Domains.create params }.to raise_error(Resend::Error::InvalidRequestError, /The name domain has been registered already./) 84 | end 85 | end 86 | 87 | describe "update domain" do 88 | 89 | before do 90 | Resend.configure do |config| 91 | config.api_key = "re_123" 92 | end 93 | end 94 | 95 | it "should update a domain record" do 96 | resp = { 97 | "object": "domain", 98 | "id": "479e3145-dd38-476b-932c-529ceb705947" 99 | } 100 | 101 | update_params = { 102 | id: '479e3145-dd38-476b-932c-529ceb705947', 103 | open_tracking: true, 104 | click_tracking: true, 105 | tls: "enforced" 106 | } 107 | 108 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 109 | domain = Resend::Domains.update(update_params) 110 | expect(domain[:id]).to eql("479e3145-dd38-476b-932c-529ceb705947") 111 | expect(domain[:object]).to eql("domain") 112 | end 113 | end 114 | 115 | describe "get domain" do 116 | it "should retrieve domain" do 117 | resp = { 118 | "object": "domain", 119 | "id": "d91cd9bd-1176-453e-8fc1-35364d380206", 120 | "name": "example.com", 121 | "status": "not_started", 122 | "created_at": "2023-04-26T20:21:26.347412+00:00", 123 | "region": "us-east-1" 124 | } 125 | allow(resp).to receive(:body).and_return(resp) 126 | allow(HTTParty).to receive(:send).and_return(resp) 127 | 128 | email = Resend::Domains.get(resp[:id]) 129 | 130 | expect(email[:object]).to eql "domain" 131 | expect(email[:id]).to eql "d91cd9bd-1176-453e-8fc1-35364d380206" 132 | expect(email[:name]).to eql "example.com" 133 | expect(email[:status]).to eql "not_started" 134 | expect(email[:created_at]).to eql "2023-04-26T20:21:26.347412+00:00" 135 | expect(email[:region]).to eql "us-east-1" 136 | end 137 | end 138 | 139 | describe "list domains" do 140 | it "should list domains" do 141 | resp = { 142 | "data": [ 143 | { 144 | "id": "d91cd9bd-1176-453e-8fc1-35364d380206", 145 | "name": "example.com", 146 | "status": "not_started", 147 | "created_at": "2023-04-26T20:21:26.347412+00:00", 148 | "region": "us-east-1" 149 | } 150 | ] 151 | } 152 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 153 | expect(Resend::Domains.list[:data].empty?).to be false 154 | expect(Resend::Domains.list[:data].length).to eql(1) 155 | end 156 | end 157 | 158 | describe "remove domain" do 159 | it "should remove domain by domain id" do 160 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return("") 161 | expect { Resend::Domains.remove }.not_to raise_error 162 | end 163 | end 164 | 165 | describe "verify domain" do 166 | it "should verify domain by domain id" do 167 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return("") 168 | expect { Resend::Domains.verify }.not_to raise_error 169 | end 170 | end 171 | end 172 | -------------------------------------------------------------------------------- /spec/emails_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe "Emails" do 4 | 5 | describe "send_email" do 6 | 7 | before do 8 | Resend.api_key = "re_123" 9 | end 10 | 11 | it "should send email" do 12 | resp = {"id"=>"872d1f17-0f08-424c-a18c-d425324acab6"} 13 | params = { 14 | "from": "from@e.io", 15 | "to": ["email1@email.com"], 16 | "text": "test", 17 | "subject": "test", 18 | "tags": { 19 | "country": "br" 20 | } 21 | } 22 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 23 | expect(Resend::Emails.send(params)[:id]).to eql(resp[:id]) 24 | end 25 | 26 | it "should update email" do 27 | resp = {"id"=>"872d1f17-0f08-424c-a18c-d425324acab6", "object": "email"} 28 | params = { 29 | "id": "872d1f17-0f08-424c-a18c-d425324acab6", 30 | "scheduled_at": "2024-11-05T11:52:01.858Z" 31 | } 32 | allow_any_instance_of(Resend::Request).to receive(:perform).and_return(resp) 33 | expect(Resend::Emails.update(params)[:id]).to eql(resp[:id]) 34 | end 35 | 36 | it "should retrieve email" do 37 | resp = { 38 | "object": "email", 39 | "id": "4ef9a417-02e9-4d39-ad75-9611e0fcc33c", 40 | "to": ["james@bond.com"], 41 | "from": "onboarding@resend.dev", 42 | "created_at": "2023-04-03T22:13:42.674981+00:00", 43 | "subject": "Hello World", 44 | "html": "Congrats on sending your first email!", 45 | "text": nil, 46 | "bcc": [nil], 47 | "cc": [nil], 48 | "reply_to": [nil], 49 | "last_event": "delivered" 50 | } 51 | allow(resp).to receive(:body).and_return(resp) 52 | allow(HTTParty).to receive(:send).and_return(resp) 53 | email = Resend::Emails.get(resp[:id]) 54 | expect(email[:subject]).to eql "Hello World" 55 | expect(email[:id]).to eql "4ef9a417-02e9-4d39-ad75-9611e0fcc33c" 56 | end 57 | 58 | it "should cancel email" do 59 | resp = { 60 | "object": "email", 61 | "id": "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794" 62 | } 63 | allow(resp).to receive(:body).and_return(resp) 64 | allow(HTTParty).to receive(:send).and_return(resp) 65 | 66 | email = Resend::Emails.cancel(resp[:id]) 67 | expect(email[:id]).to eql "49a3999c-0ce1-4ea6-ab68-afcd6dc2e794" 68 | end 69 | 70 | it "should raise when to is missing" do 71 | resp = { 72 | "statusCode"=>422, 73 | "name"=>"missing_required_field", 74 | "message"=>"Missing `to` field" 75 | } 76 | allow(resp).to receive(:body).and_return(resp) 77 | params = { 78 | "from": "from@e.io", 79 | "text": "test", 80 | "subject": "test", 81 | "tags": { 82 | "country": "br" 83 | } 84 | } 85 | allow(HTTParty).to receive(:send).and_return(resp) 86 | expect { Resend::Emails.send params }.to raise_error(Resend::Error::InvalidRequestError, /Missing `to` field/) 87 | end 88 | 89 | it "should raise when from is missing" do 90 | resp = { 91 | "statusCode"=>422, 92 | "name"=>"missing_required_field", 93 | "message"=>"Missing `from` field" 94 | } 95 | allow(resp).to receive(:body).and_return(resp) 96 | params = { 97 | "to": ["from@e.io"], 98 | "text": "test", 99 | "subject": "test", 100 | "tags": { 101 | "country": "br" 102 | } 103 | } 104 | allow(HTTParty).to receive(:send).and_return(resp) 105 | expect { Resend::Emails.send params }.to raise_error(Resend::Error::InvalidRequestError, /Missing `from` field/) 106 | end 107 | 108 | it "does not send the Idempotency-Key header when :idempotency_key is not provided" do 109 | resp = { 110 | "id"=>"872d1f17-0f08-424c-a18c-d425324acab6", "object": "email" 111 | } 112 | 113 | allow(resp).to receive(:body).and_return(resp) 114 | allow(HTTParty).to receive(:send).and_return(resp) 115 | 116 | Resend::Emails.send({ from: "me" }) 117 | 118 | expect(HTTParty).to have_received(:send).with( 119 | :post, 120 | "#{Resend::Request::BASE_URL}emails", 121 | { 122 | headers: { 123 | "Content-Type" => "application/json", 124 | "Accept" => "application/json", 125 | "Authorization" => "Bearer re_123", 126 | "User-Agent" => "resend-ruby:#{Resend::VERSION}", 127 | }, 128 | body: { from: "me" }.to_json 129 | } 130 | ) 131 | end 132 | 133 | it "does not send the Idempotency-Key header when :idempotency_key is nil" do 134 | resp = { 135 | "id"=>"872d1f17-0f08-424c-a18c-d425324acab6", "object": "email" 136 | } 137 | 138 | allow(resp).to receive(:body).and_return(resp) 139 | allow(HTTParty).to receive(:send).and_return(resp) 140 | 141 | Resend::Emails.send({ from: "me" }, options: { idempotency_key: nil }) 142 | 143 | expect(HTTParty).to have_received(:send).with( 144 | :post, 145 | "#{Resend::Request::BASE_URL}emails", 146 | { 147 | headers: { 148 | "Content-Type" => "application/json", 149 | "Accept" => "application/json", 150 | "Authorization" => "Bearer re_123", 151 | "User-Agent" => "resend-ruby:#{Resend::VERSION}", 152 | }, 153 | body: { from: "me" }.to_json 154 | } 155 | ) 156 | end 157 | 158 | it "does not send the Idempotency-Key header when :idempotency_key is an empty string" do 159 | resp = { 160 | "id"=>"872d1f17-0f08-424c-a18c-d425324acab6", "object": "email" 161 | } 162 | 163 | allow(resp).to receive(:body).and_return(resp) 164 | allow(HTTParty).to receive(:send).and_return(resp) 165 | 166 | Resend::Emails.send({ from: "me" }, options: { idempotency_key: "" }) 167 | 168 | expect(HTTParty).to have_received(:send).with( 169 | :post, 170 | "#{Resend::Request::BASE_URL}emails", 171 | { 172 | headers: { 173 | "Content-Type" => "application/json", 174 | "Accept" => "application/json", 175 | "Authorization" => "Bearer re_123", 176 | "User-Agent" => "resend-ruby:#{Resend::VERSION}", 177 | }, 178 | body: { from: "me" }.to_json 179 | } 180 | ) 181 | end 182 | 183 | it "does send the Idempotency-Key header when :idempotency_key is provided" do 184 | resp = { 185 | "id"=>"872d1f17-0f08-424c-a18c-d425324acab6", "object": "email" 186 | } 187 | 188 | allow(resp).to receive(:body).and_return(resp) 189 | allow(HTTParty).to receive(:send).and_return(resp) 190 | 191 | Resend::Emails.send({ from: "me" }, options: { idempotency_key: "123" }) 192 | 193 | expect(HTTParty).to have_received(:send).with( 194 | :post, 195 | "#{Resend::Request::BASE_URL}emails", 196 | { 197 | headers: { 198 | "Content-Type" => "application/json", 199 | "Accept" => "application/json", 200 | "Authorization" => "Bearer re_123", 201 | "User-Agent" => "resend-ruby:#{Resend::VERSION}", 202 | "Idempotency-Key" => "123" 203 | }, 204 | body: { from: "me" }.to_json 205 | } 206 | ) 207 | end 208 | end 209 | end 210 | -------------------------------------------------------------------------------- /spec/railtie/mailer_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails" 4 | require "resend" 5 | require "resend/railtie" 6 | require "spec_helper" 7 | require "action_mailer" 8 | require "pry-byebug" 9 | 10 | ActionMailer::Base.raise_delivery_errors = true 11 | ActionMailer::Base.delivery_method = :test 12 | 13 | class TestMailer < ActionMailer::Base 14 | default from: "test@example.com" 15 | 16 | def html_text_msg(to, subject) 17 | headers = { 18 | "X-Entity-Ref-ID": "123", 19 | } 20 | mail(to: to, subject: subject, headers: headers) do |format| 21 | format.html { render html: "

HTML!

".html_safe } 22 | format.text { render plain: "text" } 23 | end 24 | end 25 | 26 | def text_only(to, subject) 27 | mail(to: to, subject: subject) do |format| 28 | format.text { render plain: "text" } 29 | end 30 | end 31 | 32 | def html_only(to, subject) 33 | mail(to: to, subject: subject) do |format| 34 | format.html { render html: "

HTML!

".html_safe } 35 | end 36 | end 37 | 38 | def with_headers_method(to, subject) 39 | headers["X-Entity-Ref-ID"] = "123" 40 | mail(to: to, subject: subject) do |format| 41 | format.text { render plain: "txt" } 42 | format.html { render html: "

html

".html_safe } 43 | end 44 | end 45 | 46 | def with_overwritten_headers(to, subject) 47 | headers["X-Entity-Ref-ID"] = "123" 48 | mail(to: to, subject: subject, headers: {"X-Entity-Ref-ID": "overwritten"}) do |format| 49 | format.text { render plain: "txt" } 50 | format.html { render html: "

html

".html_safe } 51 | end 52 | end 53 | 54 | def with_nil_header_values(to, subject) 55 | headers["X-Entity-Ref-ID"] = nil 56 | mail(to: to, subject: subject, headers: { 57 | "custom-header1": nil, 58 | "custom-header2": "v2" 59 | }) do |format| 60 | format.text { render plain: "txt" } 61 | format.html { render html: "

html

".html_safe } 62 | end 63 | end 64 | 65 | def with_attachment(to, subject) 66 | attachments['invoice.pdf'] = { 67 | :content => File.read('resources/invoice.pdf'), 68 | :mime_type => 'application/pdf', 69 | } 70 | mail(to: to, subject: subject) do |format| 71 | format.text { render plain: "txt" } 72 | format.html { render html: "

html

".html_safe } 73 | end 74 | end 75 | 76 | def with_options_hash 77 | headers = { 78 | "X-Entity-Ref-ID": "123", 79 | } 80 | options = { 81 | idempotency_key: "123", 82 | ignored_key: "ignored", 83 | } 84 | mail(to: "to", subject: "subj", headers: headers, options: options) do |format| 85 | format.text { render plain: "text" } 86 | end 87 | end 88 | end 89 | 90 | class TestMailerWithDisplayName < TestMailer 91 | default from: "Test " 92 | end 93 | 94 | RSpec.describe "Resend::Mailer" do 95 | before do 96 | @mailer = Resend::Mailer.new({ api_key: "123" }) 97 | end 98 | 99 | it "properly creates a message body" do 100 | message = TestMailer.html_text_msg("test@example.org", "Test!") 101 | body = @mailer.build_resend_params(message) 102 | expect(body[:from]).to eql("test@example.com") 103 | expect(body[:to]).to eql(["test@example.org"]) 104 | expect(body[:html]).to eql("

HTML!

") 105 | expect(body[:text]).to eql("text") 106 | expect(body[:headers]["X-Entity-Ref-ID"]).to eql("123") 107 | end 108 | 109 | it "properly creates a html only msg" do 110 | message = TestMailer.html_only("test@example.org", "Test!") 111 | body = @mailer.build_resend_params(message) 112 | expect(body[:from]).to eql("test@example.com") 113 | expect(body[:to]).to eql(["test@example.org"]) 114 | expect(body[:html]).to eql("

HTML!

") 115 | expect(body[:text]).to be nil 116 | end 117 | 118 | it "properly creates options hash" do 119 | message = TestMailer.with_options_hash 120 | options = @mailer.get_options(message) 121 | expect(options[:idempotency_key]).to eql("123") 122 | expect(options[:ignored_key]).to be nil 123 | expect(options.keys).to match_array([:idempotency_key]) 124 | end 125 | 126 | it "properly creates a text only msg" do 127 | message = TestMailer.text_only("test@example.org", "Test!") 128 | body = @mailer.build_resend_params(message) 129 | expect(body[:from]).to eql("test@example.com") 130 | expect(body[:to]).to eql(["test@example.org"]) 131 | expect(body[:html]).to be nil 132 | expect(body[:text]).to eql("text") 133 | end 134 | 135 | it "properly creates a html text with attachments msg" do 136 | message = TestMailer.with_attachment("test@example.org", "Test!") 137 | body = @mailer.build_resend_params(message) 138 | expect(body[:from]).to eql("test@example.com") 139 | expect(body[:to]).to eql(["test@example.org"]) 140 | expect(body[:html]).to eql("

html

") 141 | expect(body[:text]).to eql("txt") 142 | expect(body[:attachments].length).to eql(1) 143 | expect(body[:attachments].first[:filename]).to eql("invoice.pdf") 144 | expect(body[:attachments].first[:content].length > 0).to be true 145 | end 146 | 147 | it "properly handles from display name" do 148 | message = TestMailerWithDisplayName 149 | .text_only("test@example.org", "Test!") 150 | 151 | body = @mailer.build_resend_params(message) 152 | expect(body[:from]).to eql("Test ") 153 | expect(body[:to]).to eql(["test@example.org"]) 154 | expect(body[:html]).to be nil 155 | expect(body[:text]).to eql("text") 156 | expect(body[:headers]).to be nil 157 | end 158 | 159 | it "properly creates header through headers method" do 160 | message = TestMailer.with_headers_method("test@example.org", "Test!") 161 | body = @mailer.build_resend_params(message) 162 | expect(body[:from]).to eql("test@example.com") 163 | expect(body[:to]).to eql(["test@example.org"]) 164 | expect(body[:html]).to eql("

html

") 165 | expect(body[:text]).to eql("txt") 166 | expect(body[:headers]["X-Entity-Ref-ID"]).to eql("123") 167 | end 168 | 169 | it "#build_resend_params properly overwrites #headers" do 170 | message = TestMailer.with_overwritten_headers("test@example.org", "Test!") 171 | body = @mailer.build_resend_params(message) 172 | expect(body[:from]).to eql("test@example.com") 173 | expect(body[:tags]).to eql(nil) 174 | expect(body[:to]).to eql(["test@example.org"]) 175 | expect(body[:html]).to eql("

html

") 176 | expect(body[:text]).to eql("txt") 177 | expect(body[:headers]["X-Entity-Ref-ID"]).to eql("overwritten") 178 | end 179 | 180 | it "#build_resend_params handles nil values" do 181 | message = TestMailer.with_nil_header_values("test@example.org", "Test!") 182 | body = @mailer.build_resend_params(message) 183 | expect(body[:headers].keys).to eq(["custom-header2"]) 184 | end 185 | end 186 | -------------------------------------------------------------------------------- /spec/request_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "resend/request" 4 | require "resend/client" 5 | 6 | RSpec.describe Resend::Request do 7 | 8 | before do 9 | Resend.api_key = "re_123" 10 | end 11 | 12 | context '#perform' do 13 | context 'response is not json' do 14 | before do 15 | response = double(HTTParty::Response) 16 | html = "not json" 17 | allow(response).to receive(:body).and_return(html) 18 | allow(HTTParty).to receive(:send).and_return(response) 19 | end 20 | 21 | it 'should be rescuable' do 22 | req = described_class.new 23 | expect { req.perform }.to raise_error(Resend::Error::InternalServerError, /Resend API returned an unexpected response/) 24 | end 25 | end 26 | end 27 | 28 | context 'initialize' do 29 | it 'sets default headers' do 30 | req = described_class.new 31 | expect(req.instance_variable_get(:@headers)).to include( 32 | "Content-Type" => "application/json", 33 | "Accept" => "application/json", 34 | "User-Agent" => "resend-ruby:#{Resend::VERSION}", 35 | "Authorization" => "Bearer re_123" 36 | ) 37 | expect(req.instance_variable_get(:@headers)).not_to include("Idempotency-Key") 38 | end 39 | 40 | it 'should set idempotency key' do 41 | req = described_class.new("path", {}, "POST", options: { idempotency_key: "123" }) 42 | expect(req.instance_variable_get(:@headers)["Idempotency-Key"]).to eq("123") 43 | end 44 | 45 | it 'sets verb' do 46 | req = described_class.new("path", {}, "POST") 47 | expect(req.instance_variable_get(:@verb)).to eq("POST") 48 | end 49 | end 50 | 51 | context 'handle_error!' do 52 | 53 | it "Resend::Error::InvalidRequestError 400" do 54 | req = described_class.new 55 | resp = { 56 | :statusCode => 400, 57 | :message => "400" 58 | } 59 | expect { req.handle_error!(resp) }.to raise_error(Resend::Error::InvalidRequestError, /400/) 60 | end 61 | 62 | it "Resend::Error::InvalidRequestError 422" do 63 | req = described_class.new 64 | resp = { 65 | :statusCode => 422, 66 | :message => "422" 67 | } 68 | expect { req.handle_error!(resp) }.to raise_error(Resend::Error::InvalidRequestError, /422/) 69 | end 70 | 71 | it "Resend::Error::RateLimitExceededError 429" do 72 | req = described_class.new 73 | resp = { 74 | :statusCode => 429, 75 | :message => "429" 76 | } 77 | expect { req.handle_error!(resp) }.to raise_error(Resend::Error::RateLimitExceededError, /429/) 78 | end 79 | 80 | it "Resend::Error::InternalServerError 500" do 81 | req = described_class.new 82 | resp = { 83 | :statusCode => 500, 84 | :message => "500" 85 | } 86 | expect { req.handle_error!(resp) }.to raise_error(Resend::Error::InternalServerError, /500/) 87 | end 88 | 89 | it 'unkown error should return Resend::Error' do 90 | req = described_class.new 91 | resp = { 92 | :statusCode => 999, 93 | :message => "999" 94 | } 95 | expect { req.handle_error!(resp) }.to raise_error(Resend::Error, "999") 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /spec/resend_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe Resend do 4 | it "has a version number" do 5 | expect(Resend::VERSION).not_to be nil 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "resend" 4 | 5 | RSpec.configure do |config| 6 | # Enable flags like --only-failures and --next-failure 7 | config.example_status_persistence_file_path = ".rspec_status" 8 | 9 | # Disable RSpec exposing methods globally on `Module` and `main` 10 | config.disable_monkey_patching! 11 | 12 | config.expect_with :rspec do |c| 13 | c.syntax = :expect 14 | end 15 | 16 | config.filter_run_when_matching :focus 17 | end 18 | --------------------------------------------------------------------------------